敗者死於絕望,勝者死於渴望。c++
一看這個題就來者不善,對於第一題第一眼覺得是一個大模擬,沒想到是最小生成樹。數組
對於第二題,先是看到了狀壓能夠搞到的 20pts 而後對着暴力一頓猛調後來發現是題面理解錯了。優化
最後 40min 的時候仍是把目光投向了這個題的另外一個部分分,而後搞了一個區間 DP 可是應該是 線性 DP。spa
而後我就又涼了,第三題第一眼是輸,第二眼是 DFS 序,第三眼是 DFS 序上的線段樹,再而後就老老實實去整暴力了。code
後來題目名字在網上一搜,居然都是歌名。。blog
正解是最小生成樹,把上下邊界看成兩個點來看,而後就搞各個點之間的距離再用 Prim 求最小生成樹了。get
注意一點,這裏用 Kruskal會 TLE 由於多了一個 \(logn\) 的複雜度。qt
下面主要證實一下最小生成樹解法的正確性。it
好比下面的這個圖ast
全部最小邊權的邊所鏈接的就好似鏈接了上下兩個邊界的一個阻斷線,顯然,咱們是必定要從其中穿過去的。
那麼,咱們必定要選擇其中權值最大的邊的中點穿過。
所以,答案就是最小生成樹上最大邊權的一半
#include<bits/stdc++.h> #define int long long #define ls x<<1 #define rs x<<1|1 using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=6e3+10,INF=1e18; int n,m,tot; bool vis[N]; double ans,dis[N]; struct Node { int x,y; }s[N]; double dist(double x,double y,double x2,double y2) { return sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2)); } signed main() { n=read(); m=read(); tot=read(); for(int i=1;i<=tot;i++) { s[i].x=read(); s[i].y=read(); dis[i]=s[i].y; } dis[tot+1]=m; for(int i=1;i<=tot+1;i++) { int pos=0; for(int j=1;j<=tot+1;j++) if(!vis[j]&&(!pos||dis[j]<dis[pos])) pos=j; vis[pos]=true; ans=max(ans,dis[pos]); if(pos==tot+1) break; for(int j=1;j<=tot;j++) if(!vis[j]) dis[j]=min(dis[j],dist(s[pos].x,s[pos].y,s[j].x,s[j].y)); dis[tot+1]=min(dis[tot+1],1.0*m-s[pos].y); } printf("%.10lf",ans/2); return 0; }
本題的思路或許有一點難懂,就是那種只可意會不可言傳的感受。
首先要明白一個概念:極長上升序列。(對於以後的點都不能夠比這個點大)
再看一下 40pts 的作法,直接暴力 DP 設 \(f_i\) 數組表示以 i 結尾的極長上升序列的價值。
而後,先初始化一下每一個序列的開始,須要知足以前全部的點的值都小於它。
接下來在枚舉結尾點的前提下,一個一個向前跳,要知足從該節點一直到 i-1 節點沒有比該節點還要大的點。
其實就是爲了防止隔級跳的狀況。
在尋找結尾節點的時候和尋找起始節點的相反(保證 i 到 n 沒有比它大的點)
而後咱們就獲得了暴力DP的\(code\)
可是 \(n^2\) 的彷佛太慢了,因而咱們就能夠考慮線段樹優化。
好像是相似於一種叫作李超線段樹的東西,具體實現細節見代碼
#include<bits/stdc++.h> #define int long long #define ls x<<1 #define rs x<<1|1 #define f() cout<<"Fuck"<<endl; using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+10,INF=1e18;//v爲新的權值 int n,las,v,s[N],val[N],q[N<<2];//q數組記錄以前的最有解 struct Segment_Tree { int las,dat;//dat就是前面的 f 數組而且必須以這個節點結尾,las表示每一個的最後的那個點 }tre[N<<2];//las數組表示當前節點所在的極長序列的末尾 int solve(int x,int l,int r,int pos) { if(l==r) return (tre[x].las>pos)?tre[x].dat:INF; int mid=(l+r)>>1; if(tre[rs].las<=pos) return solve(ls,l,mid,pos);//不符合直接左兒子 return min(q[x],solve(rs,mid+1,r,pos));//左兒子的部分必定是極長上升序列的部分 } void push_up(int x,int l,int r) { int mid=(l+r)>>1; tre[x].las=max(tre[ls].las,tre[rs].las); q[x]=solve(ls,l,mid,tre[rs].las); } void insert(int x,int l,int r,int pos,int num,int vall) { if(l==r) { tre[x].dat=vall; tre[x].las=num; return; } int mid=(l+r)>>1; if(pos<=mid) insert(ls,l,mid,pos,num,vall); else insert(rs,mid+1,r,pos,num,vall); push_up(x,l,r); } void query(int x,int l,int r,int pos) { if(r<=pos) { v=min(v,solve(x,l,r,las));//只要在這個點以前就查詢最小的價值 las=max(las,tre[x].las); return ; } int mid=(l+r)>>1; if(mid<pos) query(rs,mid+1,r,pos); query(ls,l,mid,pos);//至關於向前跳的一個過程 } signed main() { n=read(); for(int i=1;i<=n;i++) s[i]=read(); for(int i=1;i<=n;i++) val[i]=read(); memset(q,0x7f,sizeof(q)); for(int i=1;i<=n;i++) { las=0; v=INF; query(1,1,n,s[i]);//求值儲存到v而且對於前面的進行更新 if(v>=INF) v=0; insert(1,1,n,s[i],i,v+val[i]); } las=0; v=INF; query(1,1,n,n); printf("%lld",v); return 0; }
這個題的第一思路仍是在樹上維護某些東西,可是能想到的最優的也就只是在每條鏈上跳了。
可是在鏈上跳能夠得到 50pts 的鉅額分數(前提是你不和我同樣開小數組)
正解就是什麼可持久化棧維護凸包。
But,在我頹了別的題解以後發現這並無什麼用。
直接在樹上用倍增維護凸包就很是的棒,嗯,就很棒。
對於下圖,顯然咱們應該維護一個下凸包,對於 D 點的解從 C 點轉移比從以前任意一點轉移都要更優。
如今舉 C 和 B 點轉移來對比也就是:\(\dfrac{c_D-c_B}{dep_D-dep_B}\ge \dfrac{c_D-c_C}{dep_D-dep_C}\) 就能夠進行更新。
再次基礎上優化倍增就行了。
#include<bits/stdc++.h> #define int long long using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=5e5+10,INF=1e18; int n,s[N],fa[N],dep[N],f[N][25]; int tot,ver[N],head[N],nxt[N]; double ans[N]; inline void add_edge(int x,int y) { ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } bool judge(int x,int y,int z) { return (1.0*s[z]-1.0*s[x])*(1.0*dep[z]-1.0*dep[y])>=(1.0*s[z]-1.0*s[y])*(1.0*dep[z]-1.0*dep[x]); } void dfs(int x) { dep[x]=dep[fa[x]]+1; int pos=fa[x]; for(int i=20;i>=0;i--) { int temp=f[pos][i]; if(temp<=1) continue; if(judge(f[temp][0],temp,x)) pos=temp; } if(pos!=1&&judge(f[pos][0],pos,x)) pos=f[pos][0]; f[x][0]=pos; for(int i=0;f[x][i];i++) f[x][i+1]=f[f[x][i]][i]; for(int i=head[x];i;i=nxt[i]) dfs(ver[i]); } signed main() { n=read(); for(int i=1;i<=n;i++) s[i]=read(); for(int i=2;i<=n;i++) { fa[i]=read(); add_edge(fa[i],i); } dfs(1); for(int i=2;i<=n;i++) printf("%.10lf\n", (1.0*s[f[i][0]]-1.0*s[i])/(1.0*dep[i]-dep[f[i][0]]) ); return 0; }