凉
bzoj1999 先把树的直径求出来,从左往右枚举,对于当前位置i,找到满足限制并且最远的点j,当前位置最大值就是max(i~j区间内除直径外的子树路径长度最大值,1~i的长度,j~n的长度)
然而,对于树的直径有一个很有用的性质,1~i区间内除直径外的子树路径长度最大值必然不会比1~i的长度大,否则就不是直径了。j~n同理
所以可以把i~j区间转换成1~n区间,成为定值。求完树的直径后O(n)即可出解
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; int n,S; struct node { int x,y,d,next; }a[1100000];int len,last[510000]; void ins(int x,int y,int d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } int L,ld,R,rd; void getL(int x,int fr,int d) { if(L==0||d>ld)L=x,ld=d; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=fr) getL(y,x,d+a[k].d); } } int pre[510000]; void getR(int x,int fr,int d) { if(R==0||d>rd)R=x,rd=d; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=fr) { pre[y]=k; getR(y,x,d+a[k].d); } } } bool v[510000]; int getmax(int x,int fr) { int mx=0; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(v[y]==false&&y!=fr) mx=max(mx,getmax(y,x)+a[k].d); } return mx; } int ulen,u[510000],dis[510000]; int main() { // freopen("core.in","r",stdin); // freopen("core.out","w",stdout); int x,y,dd; scanf("%d%d",&n,&S); len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&dd); ins(x,y,dd);ins(y,x,dd); } L=0;getL(1,0,0); R=0;getR(L,0,0); memset(v,false,sizeof(v)); ulen=0;u[++ulen]=R;dis[ulen]=0;v[R]=true; int k=pre[R]; while(a[k].x!=L) { u[++ulen]=a[k].x; dis[ulen]=a[k].d+dis[ulen-1]; v[a[k].x]=true; k=pre[a[k].x]; } u[++ulen]=a[k].x; dis[ulen]=a[k].d+dis[ulen-1]; v[a[k].x]=true; int mx=0,j=1; for(int i=1;i<=ulen;i++)mx=max(mx,getmax(u[i],0)); int mn=2147483647; for(int i=1;i<=ulen;i++) { while(j<ulen&&dis[j+1]-dis[i]<=S)j++; mn=min(mn,max(mx,max(dis[i],dis[ulen]-dis[j]))); } printf("%d\n",mn); return 0; }
poj3417 环的思想很重要。对于一条非树边,可以理解成和树上路径构成一个环,若删掉一条树边,删掉当前这条非树边,树边能够选择的就是在x,y树上最短路的边。树上路径修改,采用树上差分的做法。此后,遍历每一条边,计算答案。对于只被1个非树边覆盖的树边,ans++,只被0个覆盖的,所有非树边都可以随意。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct node { int x,y,next; }a[210000];int len,last[110000]; void ins(int x,int y) { len++; a[len].x=x;a[len].y=y; a[len].next=last[x];last[x]=len; } int f[25][110000],dep[110000],Bin[30]; void dfs(int x) { for(int i=1;dep[x]>=Bin[i];i++)f[i][x]=f[i-1][f[i-1][x]]; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f[0][x]) { f[0][y]=x; dep[y]=dep[x]+1; dfs(y); } } } int LCA(int x,int y) { if(dep[x]<dep[y])swap(x,y); for(int i=22;i>=0;i--) if(dep[x]-dep[y]>=Bin[i])x=f[i][x]; if(x==y)return x; for(int i=22;i>=0;i--) if(dep[x]>=Bin[i]&&f[i][x]!=f[i][y])x=f[i][x],y=f[i][y]; return f[0][x]; } int ans,m,d[110000]; void getans(int x) { for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f[0][x]) getans(y), d[x]+=d[y]; } if(x!=1) { if(d[x]==0)ans+=m; else if(d[x]==1)ans++; } } int main() { Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2; int n,x,y; scanf("%d%d",&n,&m); len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); ins(x,y);ins(y,x); } dfs(1); for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); d[x]++;d[y]++; d[LCA(x,y)]-=2; } ans=0; getans(1); printf("%d\n",ans); return 0; }
CH Round #56-C 对于当前出现的异象石,按照dfs序排序,对于相邻的点求距离(包括首尾),答案就是总距离/2。动态维护这个序列,就是平衡树啊。操作简单,什么set啊,或者线段树啊奇淫维护就好
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<set> using namespace std; typedef long long LL; struct node { int x,y,next;LL d; }a[210000];int len,last[110000]; void ins(int x,int y,LL d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } int Bin[25]; int f[25][110000],dep[110000]; int z,ys[110000];LL d[110000]; void dfs(int x) { for(int i=1;dep[x]>=Bin[i];i++)f[i][x]=f[i-1][f[i-1][x]]; ys[x]=++z; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f[0][x]) { f[0][y]=x; dep[y]=dep[x]+1; d[y]=d[x]+a[k].d; dfs(y); } } } int LCA(int x,int y) { if(dep[x]<dep[y])swap(x,y); for(int i=22;i>=0;i--) if(dep[x]-dep[y]>=Bin[i])x=f[i][x]; if(x==y)return x; for(int i=22;i>=0;i--) if(dep[x]>=Bin[i]&&f[i][x]!=f[i][y])x=f[i][x],y=f[i][y]; return f[0][x]; } LL getdis(int x,int y){return d[x]+d[y]-d[LCA(x,y)]*2;} struct myset { int t,id; myset(){} myset(int T,int ID){t=T;id=ID;} friend bool operator>(myset s1,myset s2){return s1.t>s2.t;} friend bool operator<(myset s1,myset s2){return s1.t<s2.t;} }L,R,mL,mR; set<myset>s; int main() { Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2; int n,x,y;LL dd; scanf("%d",&n); len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { scanf("%d%d%lld",&x,&y,&dd); ins(x,y,dd);ins(y,x,dd); } f[0][1]=0;z=0;d[1]=0; dfs(1); int Q;LL ans=0; char ss[10]; scanf("%d",&Q); while(Q--) { scanf("%s",ss+1); if(ss[1]=='?')printf("%lld\n",ans/2); else if(ss[1]=='+') { scanf("%d",&x); if(!s.empty()) { mL=*s.begin();mR=*--s.end(); if(ys[x]<mL.t)L=mR; else L=*--s.lower_bound(myset(ys[x],x)); if(mR.t<ys[x])R=mL; else R=*s.lower_bound(myset(ys[x],x)); ans-=getdis(L.id,R.id); ans+=getdis(L.id,x); ans+=getdis(x,R.id); } s.insert(myset(ys[x],x)); } else { scanf("%d",&x); s.erase(myset(ys[x],x)); if(!s.empty()) { mL=*s.begin();mR=*--s.end(); if(ys[x]<mL.t)L=mR; else L=*--s.lower_bound(myset(ys[x],x)); if(mR.t<ys[x])R=mL; else R=*s.lower_bound(myset(ys[x],x)); ans+=getdis(L.id,R.id); ans-=getdis(L.id,x); ans-=getdis(x,R.id); } } } return 0; }
bzoj1977 同样是环的思想。先求出一棵最小生成树,对于非树边,必然是从和树边构成的环中,断掉最大的。然而题目要求严格次小生成树,当前非树边边权可能和最大边权相等,因此需要多维护一个严格次大值。这个和倍增求最近公共祖先的原理是一样的。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; struct node { int x,y,next;LL d; }a[210000];int len,last[110000]; void ins(int x,int y,LL d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } int Bin[30]; int f[25][110000],dep[110000]; LL mx[25][110000],sx[25][110000]; LL smx(LL m1,LL s1,LL m2,LL s2) { LL ret=max(s1,s2); if(m1!=m2)ret=max(ret,min(m1,m2)); return ret; } void dfs(int x) { for(int i=1;Bin[i]<dep[x];i++) { f[i][x]=f[i-1][f[i-1][x]]; int m1=mx[i-1][x],m2=mx[i-1][f[i-1][x]]; int s1=sx[i-1][x],s2=sx[i-1][f[i-1][x]]; mx[i][x]=max(m1,m2); sx[i][x]=smx(m1,s1,m2,s2); } for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(f[0][x]!=y) { f[0][y]=x; mx[0][y]=a[k].d; dep[y]=dep[x]+1; dfs(y); } } } void getmax(int x,int y,LL &mmax,LL &smax) { if(dep[y]>dep[x])swap(x,y); for(int i=22;i>=0;i--) if(dep[x]-dep[y]>=Bin[i]) { smax=smx(mmax,smax,mx[i][x],sx[i][x]); mmax=max(mmax,mx[i][x]); x=f[i][x]; } for(int i=22;i>=0;i--) if(dep[x]>Bin[i]&&f[i][x]!=f[i][y]) { smax=smx(mmax,smax,mx[i][x],sx[i][x]); smax=smx(mmax,smax,mx[i][y],sx[i][y]); mmax=max(mmax,max(mx[i][x],mx[i][y])); x=f[i][x],y=f[i][y]; } smax=smx(mmax,smax,mx[0][x],sx[0][x]); smax=smx(mmax,smax,mx[0][y],sx[0][y]); mmax=max(mmax,max(mx[0][x],mx[0][y])); } struct edge{int x,y;LL d;}e[310000],lz[310000]; bool cmp(edge e1,edge e2){return e1.d<e2.d;} int fa[110000]; int findfa(int x) { if(x==fa[x])return x; fa[x]=findfa(fa[x]);return fa[x]; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].d); sort(e+1,e+m+1,cmp); for(int i=1;i<=n;i++)fa[i]=i; int tp=0;LL mind=0; for(int i=1;i<=m;i++) { int fx=findfa(e[i].x),fy=findfa(e[i].y); if(fx!=fy) { fa[fx]=fy;mind+=e[i].d; ins(e[i].x,e[i].y,e[i].d); ins(e[i].y,e[i].x,e[i].d); } else lz[++tp]=e[i]; } m=tp; f[0][1]=0; memset(sx,-1,sizeof(sx)); Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2; dep[1]=1;dfs(1); LL ans=(1LL<<62); for(int i=1;i<=m;i++) { LL mmax=-(1LL<<62),smax=-(1LL<<62); getmax(lz[i].x,lz[i].y,mmax,smax); if(mmax==lz[i].d) { if(smax!=-1) ans=min(ans,mind-smax+lz[i].d); } else ans=min(ans,mind-mmax+lz[i].d); } printf("%lld\n",ans); return 0; }
NOIP2009 D2T3
容易想到二分答案。对于军队而言,必然是越向上控制的叶子节点越多,那么尽量向上。先不管根,假如军队无法到达根的子节点,那么根据前面的贪心位置确定。否则,有两种情况:1、留在当前子节点,2、去往根的另一个孩子。
此时,题目就转化成:给你n个点,m个军队,军队有处在的点,有权值,点亦有权。军队可以占领自己的位置,或者占领点权+自己处于的点的点权<=自身权值的位置。
瓶颈就在于我可以留在当前的点,没有花费,特殊性造成的影响就是可能一个军队可能从一个点出发,却又回到这个点,相当于点权耗费两次,判断是否能够到达自己会出错。这样一来就无法把军队集中,统一贪心地分配。
如何去掉这个限制?对于一个军队,假如它的权值<=所处点权*2,必然不会去其他点。因为去,也只能去比当前点权值小的点,然而你还需要另一支军队占领当前点,既然这支军队可以占领当前点,那么也一定可以占领前一支军队占领的点。
处理完这个以后,我们就能够保证,即使点权耗费两次,而当前点的军队的权都是>二倍点权的,就不会出现不能到达自己的情况。由此就可以忽略第1个限制了。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; struct node { int x,y,next;LL d; }a[110000];int len,last[51000]; void ins(int x,int y,LL d) { len++; a[len].x=x;a[len].y=y;a[len].d=d; a[len].next=last[x];last[x]=len; } int Bin[25];LL d[25][51000]; int f[25][51000],dep[51000],son[51000]; void dfs(int x) { for(int i=1;Bin[i]<dep[x];i++) { d[i][x]=d[i-1][x]+d[i-1][f[i-1][x]]; f[i][x]=f[i-1][f[i-1][x]]; } son[x]=0; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y!=f[0][x]) { son[x]++; dep[y]=dep[x]+1; f[0][y]=x; d[0][y]=a[k].d; dfs(y); } } } int m,p[51000],s[51000];bool v[51000],b[51000]; struct stack{LL d;int id;}sta[51000];int top; int arlen,ctlen;LL ar[51000],ct[51000]; bool check(LL mid) { memset(v,false,sizeof(v)); for(int k=last[1];k;k=a[k].next)b[a[k].y]=false; memcpy(s,son,sizeof(s)); top=0; for(int i=1;i<=m;i++) { int x=p[i];LL w=mid; for(int j=22;j>=0;j--) if(dep[x]>Bin[j]&&w>=d[j][x]&&f[j][x]!=1)w-=d[j][x],x=f[j][x]; if(f[0][x]!=1) { bool bk=false; for(int k=x;k!=1;k=f[0][k])if(v[k]==true){bk=true;break;} if(bk==false) { v[x]=true; s[f[0][x]]--; while(s[f[0][x]]==0) { v[f[0][x]]=true; x=f[0][x],s[f[0][x]]--; if(f[0][x]==1){b[x]=true;break;} } } } else sta[++top].d=w,sta[top].id=x; } arlen=0; for(int i=1;i<=top;i++) if(b[sta[i].id]==false) { if(d[0][sta[i].id]*2>=sta[i].d)b[sta[i].id]=true; else ar[++arlen]=sta[i].d-d[0][sta[i].id]; } else ar[++arlen]=sta[i].d-d[0][sta[i].id]; sort(ar+1,ar+arlen+1); ctlen=0; for(int k=last[1];k;k=a[k].next) if(b[a[k].y]==false)ct[++ctlen]=a[k].d; sort(ct+1,ct+ctlen+1); int j=1; for(int i=1;i<=ctlen;i++) { while(j<arlen&&ar[j]<ct[i])j++; if(ar[j]<ct[i])return false; j++; if(j>arlen&&i<ctlen)return false; } return true; } int main() { int n,x,y;LL dd; scanf("%d",&n); for(int i=1;i<n;i++) { scanf("%d%d%lld",&x,&y,&dd); ins(x,y,dd);ins(y,x,dd); } Bin[0]=1;for(int i=1;i<=22;i++)Bin[i]=Bin[i-1]*2; f[0][1]=0;dep[1]=1;dfs(1); scanf("%d",&m); if(son[x]>m){printf("-1\n");return 0;} for(int i=1;i<=m;i++)scanf("%d",&p[i]); LL l=1,r=5*1e13,ans; while(l<=r) { LL mid=(l+r)/2; if(check(mid)) { ans=mid; r=mid-1; } else l=mid+1; } printf("%lld\n",ans); return 0; }