標籤(空格分隔): 517coding solution problemnode
對邊進行樹上差分。ios
考慮到一條路徑\(u - v\) 能夠將$c_u +=1 ,c_v +=1 ,c_{lca(u,v)}-=2 $c++
而後對整一棵樹求子樹和,對於每一個點的子樹和,就是這個點向上那條邊的答案。數組
複雜度\(O(n \ log \ n)\)spa
# include <bits/stdc++.h> using namespace std; const int N=1e5+10; struct rec{ int pre,to; }a[N<<1]; int head[N],tot=1,ans[N],c[N],dep[N],g[N][22],n,m; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void dfs(int u,int fa) { dep[u]=dep[fa]+1; g[u][0]=fa; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs(v,u); } } void init() { dfs(1,0); for (int i=1;i<=21;i++) for (int j=1;j<=n;j++) g[j][i]=g[g[j][i-1]][i-1]; } int lca(int u,int v) { if (dep[u]<dep[v]) swap(u,v); for (int i=21;i>=0;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u==v) return u; for (int i=21;i>=0;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; } void dfs2(int u,int fa,int to) { for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs2(v,u,i>>1);c[u]+=c[v]; } ans[to]=c[u]; } int main() { scanf("%d",&n); for (int i=1;i<n;i++) { int u,v;scanf("%d%d",&u,&v); adde(u,v); adde(v,u); } scanf("%d",&m); init(); for (int i=1;i<=m;i++) { int u,v;scanf("%d%d",&u,&v); c[u]++; c[v]++; c[lca(u,v)]-=2; } dfs2(1,0,0); for (int i=1;i<=n-1;i++) printf("%d ",ans[i]); return 0; }
首先將題目所給的相連的黑色點和白色點都縮成一個點。code
而後這棵樹就變成黑白相間的點了。排序
那麼若是對於某一點進行操做勢必能夠從改點爲中心向外輻射,不斷能夠把該點爲中心的連通塊染成同一顏色。get
for example , 1 0 1 0 1 第一次能夠變成1 0 0 0 1 第二次能夠變成 1 1 1 1 1 。string
那麼對於一棵樹,操做的最優的點顯然是樹的直徑的中點附近。it
全部問題有轉化爲將樹的直徑染色使得直徑變成同一顏色。
考慮若長度爲奇數 : 形如 1 0 1 ,答案就是 len/2
若長度爲偶數:形如 1 0 ,答案就是len/2
複雜度是\(O(n)\)
# include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct rec{ int pre,to; }a[N<<1]; int w[N],head[N],n,m,tot,d[N],belong[N]; vector<int>E[N]; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void dfs1(int u,int num) { belong[u]=num; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if ((belong[v]==-1) && (w[v]==0)) dfs1(v,num); } } void dfs2(int u,int num) { belong[u]=num; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if ((belong[v]==-1) && (w[v]==1)) dfs2(v,num); } } void dfs3(int u,int fa,int L) { d[u]=L; for (int i=0;i<E[u].size();i++) if (E[u][i]!=fa) dfs3(E[u][i],u,L+1); } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&w[i]); for (int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); adde(u,v); adde(v,u); } memset(belong,-1,sizeof(belong)); int tt=0; for (int i=1;i<=n;i++) if ((belong[i]==-1) && (w[i]==0)) tt++,dfs1(i,tt); for (int i=1;i<=n;i++) if ((belong[i]==-1) && (w[i]==1)) tt++,dfs2(i,tt); for (int u=1;u<=n;u++) for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (belong[v]!=belong[u]) E[belong[v]].push_back(belong[u]); } for (int i=1;i<=tt;i++){ int tmp=unique(E[i].begin(),E[i].end())-E[i].begin(); E[i].erase(E[i].begin()+tmp,E[i].end()); } memset(d,0,sizeof(d)); int pt=0; dfs3(1,0,1); for (int i=1;i<=tt;i++) if (d[pt]<d[i]) pt=i; memset(d,0,sizeof(d)); int ans=0; dfs3(pt,0,1); for (int i=1;i<=tt;i++) if (ans<d[i]) ans=d[i]; printf("%d\n",ans>>1); return 0; }
經典的樹上路徑交的問題。
考慮兩條路徑\([a,b] , [c,d]\)交的條件。
就是\(lca(a,b)\)在\([c,d]\)上或者\(lca(c,d)\)在\([a,b]\)上。
一種比較好的策略是儘量選擇\(lca\)深度大的路徑,這樣至少不會壞。
首先,每一條路徑最多對答案作出1的貢獻,而後深度若是越小,那麼越可能和其餘路徑相交。若是按照深度一次遞增的狀況,不會更壞。
處理的時候,若是\(u\)爲$ lca$的路徑是合法的話,那麼其子樹的其餘節點必然不可能成爲路徑的出發點,因此直接把它的子樹賦值爲false便可。
這樣子複雜度就是對的了,
複雜度 $ O(n log n) $
# include <bits/stdc++.h> using namespace std; const int N=1e5+10; struct node{ int u,v,l;}p[N]; struct rec{ int pre,to;}a[N<<1]; int n,m,tot,head[N],dep[N],g[N][22]; bool vis[N]; void clear() { memset(p,0,sizeof(p)); memset(a,0,sizeof(a)); memset(head,0,sizeof(head)); memset(dep,0,sizeof(dep)); memset(g,0,sizeof(g)); memset(vis,false,sizeof(vis)); tot=0; } void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void dfs(int u,int fa) { dep[u]=dep[fa]+1; g[u][0]=fa; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs(v,u); } } void init() { dfs(1,0); for (int i=1;i<=21;i++) for (int j=1;j<=n;j++) g[j][i]=g[g[j][i-1]][i-1]; } int lca(int u,int v) { if (dep[u]<dep[v]) swap(u,v); for (int i=21;i>=0;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u==v) return u; for (int i=21;i>=0;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; } bool cmp(node a,node b){return dep[a.l]>dep[b.l];} void draw(int u,int fa) { vis[u]=true; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa||vis[v]) continue; draw(v,u); } } int main () { while (~scanf("%d%d",&n,&m)) { clear(); for (int i=2;i<=n;i++) { int u,v; scanf("%d%d",&u,&v); adde(u,v); adde(v,u); } init(); for (int i=1;i<=m;i++) { scanf("%d%d",&p[i].u,&p[i].v); p[i].l=lca(p[i].u,p[i].v); } sort(p+1,p+1+m,cmp); int ans=0; memset(vis,false,sizeof(vis)); for (int i=1;i<=m;i++) { if (vis[p[i].u]||vis[p[i].v]) continue; ans++; draw(p[i].l,g[p[i].l][0]); } printf("%d\n",ans); } return 0; }
先將樹的任意一條直徑找出來,考慮樹的直徑必定是交於一條線段上的。
那麼從直徑兩段往中間搜必定是中間這一段路徑是惟一的。
設直徑是\([s,t]\),把這個直徑拉出來,左側是\(s\),右側是\(t\);
上述過程顯然是一個線性過程,複雜度是\(O(n)\)
# include <cstdio> # include <iostream> # include <cstring> # define int long long using namespace std; const int N=2e5+10; struct rec{ int pre,to,w;}a[N<<1]; int n,tot; int d[N],head[N],tim[N],f[N],path[N],pre[N]; void adde(int u,int v,int w) { a[++tot].pre=head[u]; a[tot].to=v; a[tot].w=w; head[u]=tot; } void dfs1(int u,int fa,int L) { d[u]=L; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs1(v,u,L+a[i].w); } } void dfs2(int u,int fa,int L) { d[u]=L; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; pre[v]=u; dfs2(v,u,L+a[i].w); } } void dfs3(int u,int fa) { int cnt=0,mx=0; bool leaf=1; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; leaf=0; dfs3(v,u); if (f[v]+a[i].w>mx) mx=f[v]+a[i].w,cnt=tim[v]; else if (f[v]+a[i].w==mx) cnt+=tim[v]; } if (leaf) f[u]=0,tim[u]=1; else f[u]=mx,tim[u]=cnt; } signed main() { scanf("%lld",&n); for (int i=1;i<n;i++) { int u,v,w; scanf("%lld%lld%lld",&u,&v,&w); adde(u,v,w); adde(v,u,w); } memset(d,0,sizeof(d)); dfs1(1,0,0); int s=0; for (int i=1;i<=n;i++) if (d[i]>d[s]) s=i; memset(d,0,sizeof(d)); dfs2(s,0,0); int t=0; pre[s]=-1; for (int i=1;i<=n;i++) if (d[i]>d[t]) t=i; printf("%lld\n",d[t]); int u=t,v; while (pre[u]!=-1) path[++path[0]]=u,u=pre[u]; path[++path[0]]=u; for (int i=1;i<=path[0]/2;i++) swap(path[i],path[path[0]-i+1]); memset(f,0,sizeof(f)); memset(tim,0,sizeof(tim)); dfs3(s,0); v=path[0]; for (int i=path[0]-1;i>=1;i--) if (tim[path[i]]-tim[path[i+1]]>0) v=i; memset(f,0,sizeof(f)); memset(tim,0,sizeof(tim)); dfs3(t,0); u=1; for (int i=2;i<=path[0];i++) if (tim[path[i]]-tim[path[i-1]]>0) u=i; printf("%lld\n",v-u); return 0; }
按照上述作法添加一次葉子,直徑最多隻可能增長1。
咱們只須要維護當前樹的直徑(的兩個端點)就能夠了。
設以前的端點是\(A,B\)當前新加入一個葉子\(x\) ,
離線處理搞搞\(lca\)就能夠了。
複雜度是\(O((2q+4) \ log \ (2q+4))\)
# include <bits/stdc++.h> using namespace std; const int N=1e6+1000; struct rec{ int pre,to;}a[N<<1]; int head[N],q[N],n,tot,dep[N],g[N][22]; void adde(int u,int v) { a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; } void dfs(int u,int fa) { g[u][0]=fa; dep[u]=dep[fa]+1; for (int i=head[u];i;i=a[i].pre) { int v=a[i].to; if (v==fa) continue; dfs(v,u); } } void init() { dfs(1,0); for (int i=1;i<=21;i++) for (int j=1;j<=n;j++) g[j][i]=g[g[j][i-1]][i-1]; } int lca(int u,int v) { if (dep[u]<dep[v]) swap(u,v); for (int i=21;i>=0;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u==v) return u; for (int i=21;i>=0;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; } int dist(int x,int y) { int l=lca(x,y); return (dep[x]-dep[1]+dep[y]-dep[1])-2*(dep[l]-1); } int main() { //freopen("1.in","r",stdin); int Q; scanf("%d",&Q); n=4; adde(1,2); adde(2,1); adde(1,3); adde(3,1); adde(1,4); adde(4,1); for (int i=1;i<=Q;i++) { scanf("%d",&q[i]); adde(n+1,q[i]); adde(q[i],n+1); adde(n+2,q[i]); adde(q[i],n+2); n+=2; } init(); int now=2,A=2,B=4,m=4; for (int i=1;i<=Q;i++) { int u=q[i],v=++m; int d1=dist(v,A),d2=dist(v,B); if (d1>now) now=d1,B=v; else if (d2>now) now=d2,A=v; v=++m; d1=dist(v,A),d2=dist(v,B); if (d1>now) now=d1,B=v; else if (d2>now) now=d2,A=v; printf("%d\n",now); } return 0; }
第一問就是裸的最大生成樹,邊權排序跑kruskal就能夠了。
第二問求一棵樹上知足對於路徑\([u,v]\)上兩個編號爲\(i,j\)的點,在知足\(i>j\)的條件下,最大化\(c_i - c_j\) 的值。
考慮到編號對答案的限制,因此咱們不妨考慮用倍增來完成限制。
能夠首先用5個倍增數組,分別表示"\(u\)向上跳\(2^j\)的節點是誰","\(u\)向上跳\(2^j\)的合法最大值","\(u\)向上跳\(2^j\)的合法最小值","\(u\)向上跳\(2^j\)的合法最大極差(祖先-孫子)","\(u\)向上跳\(2^j\)的合法最大極差(孫子-祖先)"
最後答案必定是由\([u,lca]\) , \([v,lca]\),跨\(lca\)三個部分組成的,拼湊一下便可。
複雜度是\(O(n \ log \ n)\)
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<queue> #include<algorithm> #include<stack> #define N 60000 std::queue<int>q; struct aa{ int p,next; }da[N*3]; struct noz{ int x,y,len; }line[N*2]; int fa[N],dd,tou[N],mx[N][21],mi[N][21],fm[N][21],dp[N][21],dp2[N][21]; int n,b[N],m,dep[N]; bool vis[N]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void add(int x,int y){ da[++dd].p=y;da[dd].next=tou[x];tou[x]=dd; da[++dd].p=x;da[dd].next=tou[y];tou[y]=dd; } void bfs(){ q.push(1); vis[1]=1; dep[1]=1; while (!q.empty()){ int u=q.front();q.pop(); for (int i=tou[u];i;i=da[i].next){ int v=da[i].p; if (!vis[v]){ dep[v]=dep[u]+1; q.push(v),vis[v]=1; fm[v][0]=u; mx[v][0]=std::max(b[v],b[u]); mi[v][0]=std::min(b[v],b[u]); dp[v][0]=b[u]-b[v]; dp2[v][0]=b[v]-b[u]; } } for (int i=1;i<=20;i++){ fm[u][i]=fm[fm[u][i-1]][i-1]; if (!fm[u][i])break; mx[u][i]=std::max(mx[u][i-1],mx[fm[u][i-1]][i-1]); mi[u][i]=std::min(mi[u][i-1],mi[fm[u][i-1]][i-1]); dp[u][i]=std::max(dp[u][i-1],mx[fm[u][i-1]][i-1]-mi[u][i-1]); dp[u][i]=std::max(dp[u][i],dp[fm[u][i-1]][i-1]); dp2[u][i]=std::max(dp2[u][i-1],mx[u][i-1]-mi[fm[u][i-1]][i-1]); dp2[u][i]=std::max(dp2[u][i],dp2[fm[u][i-1]][i-1]); } } } int lca(int x,int y){ if (dep[x]<dep[y])std::swap(x,y); for (int i=20;i>=0;i--) if (dep[x]-dep[y]>=1<<i)x=fm[x][i]; if (x==y)return x; for (int i=20;i>=0;i--) if (fm[x][i]!=fm[y][i]){ x=fm[x][i]; y=fm[y][i]; } if (x==y)return x; else return fm[x][0]; } int getMAX(int x,int goal){ int ma=0; for (int i=20;i>=0;i--) if (dep[x]-dep[goal]>=1<<i){ ma=std::max(ma,mx[x][i]); x=fm[x][i]; } return ma; } int getMIN(int x,int goal){ int ma=0x5f5f5f5f; for (int i=20;i>=0;i--) if (dep[x]-dep[goal]>=1<<i){ ma=std::min(ma,mi[x][i]); x=fm[x][i]; } return ma; } int opre1(int x,int goal){ int ma=0x5f5f5f5f; int num=0; for (int i=20;i>=0;i--) if (dep[x]-dep[goal]>=1<<i){ num=std::max(num,dp[x][i]); num=std::max(num,mx[x][i]-ma); ma=std::min(ma,mi[x][i]); x=fm[x][i]; } return num; } int opre2(int x,int goal){ int ma=-0x5f5f5f5f; int num=0; for (int i=20;i>=0;i--) if (dep[x]-dep[goal]>=1<<i){ num=std::max(num,dp2[x][i]); num=std::max(num,ma-mi[x][i]); ma=std::max(ma,mx[x][i]); x=fm[x][i]; } return num; } bool cmp(noz a,noz b){return a.len>b.len;} int main(){ while (scanf("%d",&n)!=EOF){ dd=0; memset(tou,0,sizeof(tou)); memset(vis,0,sizeof(vis)); memset(dep,0,sizeof(dep)); for (int i=1;i<=n;i++)fa[i]=i; for (int i=1;i<=n;i++) for (int j=0;j<=20;j++) mx[i][j]=dp[i][j]=dp2[i][j]=fm[i][j]=0,mi[i][j]=0x5f5f5f5f; for (int i=1;i<=n;i++)scanf("%d",&b[i]); scanf("%d",&m); for (int i=1;i<=m;i++)scanf("%d%d%d",&line[i].x,&line[i].y,&line[i].len); std::sort(line+1,line+1+m,cmp); int tot=0;int sum=0; for (int i=1;i<=m;i++){ int fx=find(line[i].x); int fy=find(line[i].y); if (fx!=fy){ fa[fx]=fy; ++tot; sum+=line[i].len; add(line[i].x,line[i].y); if (tot==n-1)break; } } printf("%d\n",sum); bfs(); int q; scanf("%d",&q); for (int i=1;i<=q;i++){ int x,y; scanf("%d%d",&x,&y); int LCA=lca(x,y); int x1=opre1(x,LCA); int x2=opre2(y,LCA); printf("%d\n",std::max(std::max(x1,x2),getMAX(y,LCA)-getMIN(x,LCA))); } } return 0; }