newcoder NOIP提高組模擬賽C題——保護
阿新 • • 發佈:2019-01-02
我是發了瘋才來寫這道題的
我如果用寫這道題的時間去寫dp,我估計我能寫上三四道
可怕的資料結構題
這道題的鬼畜之處在於實在是不太好寫
我們看到要求離樹根儘量的近,所以我們很容易就能想到樹上倍增,所以我們需要有一種能快速求出一條路徑能被多少條給出路徑完全覆蓋
我們知道起點是固定的,要求完全覆蓋的話我們必須要保證給定的路徑的一個端點在起點的子樹裡,同時還要求另一個端點在路徑的終點的外部,也就是說路徑的\(LCA\)深度小於等於終點
於是這樣就可以寫一個還算可觀的\(40\)分暴力了
這是考場上想出主席樹沒敢打的40分暴力,核心思想就是在起點的子樹裡找路徑的端點,之後判斷這些端點所對應的路徑的\(LCA\)
複雜度是\(O(qnlogn)\)
#include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #define max(a,b) ((a)>(b)?(a):(b)) #define re register #define maxn 50005 inline int read() { char c=getchar(); int x=0; while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar(); return x; } int head[maxn],deep[maxn],fa[maxn],maxdep=1; std::vector<int> v[maxn]; struct node { int v,nxt; }e[maxn<<1]; int num,H; inline void add_edge(int x,int y) { e[++num].v=y; e[num].nxt=head[x]; head[x]=num; } int lca[maxn]; int f[maxn][20]; int tot,n,m,X[maxn],Y[maxn]; void dfs(int x) { for(re int i=head[x];i;i=e[i].nxt) if(!deep[e[i].v]) { f[e[i].v][0]=x; deep[e[i].v]=deep[x]+1; dfs(e[i].v); } } inline int LCA(int x,int y) { if(deep[x]<deep[y]) std::swap(x,y); for(re int i=H;i>=0;i--) if(deep[f[x][i]]>=deep[y]) x=f[x][i]; if(x==y) return x; for(re int i=H;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } inline int check(int x,int now) { int ans=0; for(re int i=0;i<v[x].size();i++) if(deep[v[x][i]]<=now) ans++; for(re int i=head[x];i;i=e[i].nxt) if(deep[e[i].v]>deep[x]) ans+=check(e[i].v,now); return ans; } int main() { n=read(),m=read(); int x,y; for(re int i=1;i<n;i++) { x=read(),y=read(); add_edge(x,y),add_edge(y,x); } deep[1]=1; dfs(1); H=log2(n); for(re int i=1;i<=H;i++) for(re int j=1;j<=n;j++) f[j][i]=f[f[j][i-1]][i-1]; for(re int i=1;i<=m;i++) { X[i]=read(),Y[i]=read(); if(deep[X[i]]>deep[Y[i]]) std::swap(X[i],Y[i]); lca[i]=LCA(X[i],Y[i]); v[Y[i]].push_back(lca[i]); if(X[i]!=Y[i]) v[X[i]].push_back(lca[i]); } int Q=read(),k; while(Q--) { x=read(),k=read(); int sx=x; for(re int i=H;i>=0;i--) if(f[x][i]&&check(sx,deep[f[x][i]])>=k) x=f[x][i]; printf("%d\n",deep[sx]-deep[x]); } return 0; }
這根正解其實很接近了,我們想要快速判斷一個答案是否可行的話,我們可以利用主席樹來做
這裡的主席樹需要解決子樹內的問題,所以還是按照\(dfs\)序來建主席樹,之後主席樹裡的權值是這個路徑端點對應的\(LCA\)的深度
之後我們就可以利用主席樹差分知道一個子樹內的所有端點對應的\(LCA\)的深度小於等於某個值得有多少個了
所以就可以倍增加上主席樹判斷,時間複雜度\(O(nlogn+qlog^2n)\)
主要是太難寫了,考場上想出正解也不敢寫
程式碼
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> #include<vector> #define re register #define maxn 200005 #define max(a,b) ((a)>(b)?(a):(b)) inline int read() { char c=getchar(); int x=0; while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar(); return x; } int head[maxn],deep[maxn],maxdep=1; int sum1[maxn],X[maxn],Y[maxn]; struct node { int v,nxt; }e[maxn<<1]; int num; inline void add_edge(int x,int y) { e[++num].v=y; e[num].nxt=head[x]; head[x]=num; } std::vector<int> v[maxn]; int sum[maxn],to[maxn],_to[maxn]; //_to[i]是將序列上的點i對映到序列上,to[i]是將樹上的點對映到序列上 int top[maxn],son[maxn],lca[maxn]; int f[maxn][19]; int tot; void dfs(int x) { _to[++tot]=x; to[x]=tot; int maxx=-1; sum1[x]=1; for(re int i=head[x];i;i=e[i].nxt) if(!deep[e[i].v]) { f[e[i].v][0]=x; deep[e[i].v]=deep[x]+1; maxdep=max(maxdep,deep[e[i].v]); dfs(e[i].v); sum1[x]+=sum1[e[i].v]; if(sum1[e[i].v]>maxx) maxx=sum1[e[i].v],son[x]=e[i].v; } } void dfs2(int x,int topf) { top[x]=topf; if(!son[x]) return; dfs2(son[x],topf); for(re int i=head[x];i;i=e[i].nxt) if(deep[e[i].v]>deep[x]&&e[i].v!=son[x]) dfs2(e[i].v,e[i].v); } inline int LCA(int x,int y) { while(top[x]!=top[y]) { if(deep[top[x]]<deep[top[y]]) std::swap(x,y); x=f[top[x]][0]; } if(deep[x]>deep[y]) return y; return x; } int l[maxn<<6],r[maxn<<6],d[maxn<<6]; int rt[maxn]; int n,m,cnt; int Build(int x,int y) { int root=++cnt; int mid=x+y>>1; if(x==y) return root; l[root]=Build(x,mid); r[root]=Build(mid+1,y); return root; } int change(int pre,int x,int y,int t) { int root=++cnt; d[root]=d[pre]+1; if(x==y) return root; l[root]=l[pre]; r[root]=r[pre]; int mid=x+y>>1; if(t<=mid) l[root]=change(l[pre],x,mid,t); else r[root]=change(r[pre],mid+1,y,t); return root; } inline int query(int pre,int x,int y,int xx,int yy) { if(xx<=x&&yy>=y) return d[pre]; int mid=x+y>>1; if(yy<=mid) return query(l[pre],x,mid,xx,yy); if(xx>mid) return query(r[pre],mid+1,y,xx,yy); return query(l[pre],x,mid,xx,yy)+query(r[pre],mid+1,y,xx,yy); }//主席樹的板子 int main() { n=read(),m=read(); int x,y; for(re int i=1;i<n;i++) { x=read(),y=read(); add_edge(x,y),add_edge(y,x); } deep[1]=1; dfs(1); int H=log2(maxdep)+1; dfs2(1,1); for(re int i=1;i<=H;i++) for(re int j=1;j<=n;j++) f[j][i]=f[f[j][i-1]][i-1]; for(re int i=1;i<=m;i++) { X[i]=read(),Y[i]=read(); lca[i]=LCA(X[i],Y[i]); v[Y[i]].push_back(lca[i]),sum[Y[i]]++; if(X[i]!=Y[i]) v[X[i]].push_back(lca[i]),sum[X[i]]++; //一條路徑正反算兩次,如果是同一個點就只算一次 } rt[0]=Build(1,maxdep); int pre=0; for(re int i=1;i<=n;i++) { if(!sum[_to[i]]) { rt[i]=rt[i-1]; continue; } int T=rt[i-1]; for(re int j=0;j<v[_to[i]].size();j++) T=change(T,1,maxdep,deep[v[_to[i]][j]]); //一個點可能是多條路徑的端點 rt[i]=T; } int Q=read(),k; while(Q--) { x=read(),k=read(); int sx=x; for(re int i=H;i>=0;i--) { if(!f[x][i]) continue; int mid=query(rt[to[sx]+sum1[sx]-1],1,maxdep,1,deep[f[x][i]]); int MID=-query(rt[to[sx]-1],1,maxdep,1,deep[f[x][i]]); if(f[x][i]&&mid+MID>=k) x=f[x][i]; } printf("%d\n",deep[sx]-deep[x]); } return 0; }
newcoder的機子好像又變慢了,這個程式碼好像又會被卡一個點
但是正解的線段樹合併我不會寫啊
不過優化一下常數就又能過了