洛谷 1084 疫情控制——二分答案+贪心(贪心思路!)
题目:https://www.luogu.org/problemnew/show/P1084
二分答案、倍增往上走都很好想,关键是怎么贪心……
先写了一个贪心,让能走到根的军队中可以待在原孩子的先待在那,然后看看根的哪些孩子未满足,从剩下的中双指针地走。
忘了给根的孩子的边权排序,40分。改了以后70分……
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=5e4+5,Lm=16; int n,m,pos[N],hd[N],xnt,nxt[N<<1],to[N<<1],w[N<<1]; int sta[N],top,tmp[N],tot,jl[N],jnt,pre[N][Lm+5],bh[N]; ll dis[N],l,r,mid,ans=-1; bool vis[N],use[N]; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } bool cmp(int u,int v) {return dis[u]>dis[v];}//先走距离远的 void add(int x,int y,int z) { to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z; to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z; r+=z; } void dfs(int cr,int fa,int b) { pre[cr][0]=fa; bh[cr]=b;//bh:记录在根的哪个孩子 for(int i=1,d;i<=Lm;i++) { d=pre[cr][i-1]; if(!pre[d][i-1])break; pre[cr][i]=pre[d][i-1]; } for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=fa) { dis[v]=dis[cr]+w[i]; dfs(v,cr,fa?b:v); //r=max(r,dis[v]);//不是!!! } } void dfsx(int cr,int fa) { if(vis[cr]||!nxt[hd[cr]])return; for(int i=hd[cr],v=to[i];i;i=nxt[i],v=to[i]) if(v!=fa) { dfsx(v,cr); if(!vis[v]) return; } vis[cr]=1; } bool solve() { memset(vis,0,sizeof vis); top=0; for(int i=1,nw=pos[i],b=bh[nw];i<=m;i++,nw=pos[i],b=bh[nw]) { ll lm=dis[nw]-mid; for(int j=Lm;j>=0;j--) if(dis[pre[nw][j]]>=lm&&pre[nw][j]) nw=pre[nw][j]; vis[nw]=1; //printf("i=%d nw=%d\n",i,nw); if(nw==1) sta[++top]=pos[i];//按剩余从小到大排序过了 } for(int i=hd[1];i;i=nxt[i]) dfsx(to[i],1); for(int i=1;i<=jnt;i++) use[jl[i]]=0; jnt=0; for(int i=1;i<=top;i++) { //printf("bh[%d]=%d vis=%d\n",sta[i],bh[sta[i]],vis[bh[sta[i]]]); if(!vis[bh[sta[i]]]) { vis[bh[sta[i]]]=1,use[i]=1; jl[++jnt]=i; } } tot=0; for(int i=hd[1];i;i=nxt[i]) { //printf("to[i]=%d vis[%d]=%d\n",to[i],to[i],vis[to[i]]); if(!vis[to[i]])tmp[++tot]=w[i]; } sort(tmp+1,tmp+tot+1);//! int p0=1; //if(mid==1000)printf("tot=%d top=%d jnt=%d\n",tot,top,jnt); for(int i=1;i<=tot;i++) { //if(mid==1000)printf("tmp[%d]=%d\n",i,tmp[i]); while(p0<top&&(use[p0]||mid-dis[sta[p0]]<tmp[i])) p0++; //if(mid==1000)printf("sta[%d]=%d res=%lld\n",p0,sta[p0],mid-dis[sta[p0]]); if(p0>top||use[p0]||mid-dis[sta[p0]]<tmp[i]) { //if(mid==1000) // printf("tmp=%d sta[%d]=%d res=%lld(top=%d)\n",tmp[i],p0,sta[p0],mid-dis[sta[p0]],top); return 0; } p0++; } return 1; } int main() { //freopen("data.in","r",stdin); //freopen("testdata.in","r",stdin); //freopen("zj.out","w",stdout); n=rdn(); for(int i=1,u,v,z;i<n;i++) { u=rdn();v=rdn();z=rdn(); add(u,v,z); } dfs(1,0,0); m=rdn(); for(int i=1;i<=m;i++) pos[i]=rdn(); sort(pos+1,pos+m+1,cmp); while(l<=r) { mid=l+r>>1ll; if(solve()) ans=mid,r=mid-1; else l=mid+1; } printf("%lld\n",ans); return 0; }
然后想到了反例。就是可以让走到根、剩余较少的军队覆盖一个离根近的孩子,让那个孩子的军队走上来,去覆盖别的孩子。如果离根近的孩子的军队剩余得很多的话,就能覆盖掉别的军队覆盖不了的孩子了。
于是换贪心策略。先双指针地分配,如果一个军队走不了了,再看看它是否需要回到自己原来的孩子,不能的话就不管它了。
然而还是70分……
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=5e4+5,Lm=16; int n,m,pos[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1]; int dis[N],pre[N][Lm+5],bh[N],fe[N]; int sta[N],top,tmp[N],tot; ll ans,mid,l,r; bool vis[N]; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } void add(int x,int y,int z) { to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z; to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z; } void dfs(int cr,int f,int b) { pre[cr][0]=f; for(int i=1,d;i<=Lm;i++) { d=pre[cr][i-1]; if(!d)break; pre[cr][i]=pre[d][i-1]; } bh[cr]=b; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=f) { dis[v]=dis[cr]+w[i]; fe[v]=w[i]; dfs(v,cr,f?b:v); } } bool cmp(int u,int v){return dis[u]>dis[v];} void dfsx(int cr,int f) { if((cr>1&&vis[cr])||!nxt[hd[cr]])return; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=f) { dfsx(v,cr); if(!vis[v]&&cr>1)return; } vis[cr]=1; } bool cmp2(int u,int v){return fe[u]<fe[v];} bool solve() { memset(vis,0,sizeof vis); top=0; for(int i=1,nw=pos[i];i<=m;i++,nw=pos[i]) { ll lm=dis[nw]-mid; for(int j=Lm;j>=0;j--) if(pre[nw][j]&&dis[pre[nw][j]]>=lm) nw=pre[nw][j]; vis[nw]=1; if(nw==1)sta[++top]=pos[i]; } dfsx(1,0); tot=0; for(int i=hd[1];i;i=nxt[i]) if(!vis[to[i]]) tmp[++tot]=to[i]; sort(tmp+1,tmp+tot+1,cmp2); int p0=top; for(int i=tot;i;i--) { if(vis[tmp[i]])continue; while(p0>1&&mid-dis[sta[p0]]<fe[tmp[i]]) { if(!vis[bh[sta[p0]]]) vis[bh[sta[p0]]]=1; p0--; } if(p0<=0||mid-dis[sta[p0]]<fe[tmp[i]]) return 0; p0--; } return 1; } int main() { //freopen("testdata.in","r",stdin); n=rdn(); for(int i=1,u,v,z;i<n;i++) { u=rdn(); v=rdn(); z=rdn(); add(u,v,z); r+=z<<1; } dfs(1,0,0); m=rdn(); for(int i=1;i<=m;i++) pos[i]=rdn(); sort(pos+1,pos+m+1,cmp); while(l<=r) { mid=l+r>>1; if(solve())ans=mid,r=mid-1; else l=mid+1; } printf("%lld\n",ans); return 0; }
终于去查了题解。原来应该以军队为主,从小到大遍历军队,如果它的原来的孩子还没被管,就让它管它原来的孩子;否则让它管一个根的最小的孩子,即正常的双指针。
这样贪心的话,一个军队要么是双指针地管了最小的需要管的孩子,要么是更优地管了自己原来的孩子;既不像第一个贪心那样,可能浪费路程长的军队,又不像第二个贪心那样,可能浪费了军队可以随便回到自己原来孩子的性质。真是……
自己还要多锻炼思维呢。
代码里注意给双指针走过的孩子也打上 vis 标记。因为进来一个军队时要判断那个。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=5e4+5,Lm=16; int n,m,pos[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1]; int dis[N],pre[N][Lm+5],bh[N],fe[N]; int sta[N],top,tmp[N],tot; ll ans=-1,mid,l,r; bool vis[N]; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } void add(int x,int y,int z) { to[++xnt]=y; nxt[xnt]=hd[x]; hd[x]=xnt; w[xnt]=z; to[++xnt]=x; nxt[xnt]=hd[y]; hd[y]=xnt; w[xnt]=z; } void dfs(int cr,int f,int b) { pre[cr][0]=f; for(int i=1,d;i<=Lm;i++) { d=pre[cr][i-1]; if(!d)break; pre[cr][i]=pre[d][i-1]; } bh[cr]=b; for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=f) { dis[v]=dis[cr]+w[i]; fe[v]=w[i]; dfs(v,cr,f?b:v); } } bool cmp(int u,int v){return dis[u]>dis[v];} void dfsx(int cr,int f) { if((cr>1&&vis[cr])||(!nxt[hd[cr]]&&cr>1))return;//cr>1 for(int i=hd[cr],v;i;i=nxt[i]) if((v=to[i])!=f) { dfsx(v,cr); if(!vis[v]&&cr>1)return; } vis[cr]=1; } bool cmp2(int u,int v){return fe[u]<fe[v];} bool solve() { memset(vis,0,sizeof vis); top=0; for(int i=1,nw=pos[i];i<=m;i++,nw=pos[i]) { ll lm=dis[nw]-mid; for(int j=Lm;j>=0;j--) if(pre[nw][j]&&dis[pre[nw][j]]>=lm) nw=pre[nw][j]; vis[nw]=1; if(nw==1)sta[++top]=pos[i]; } //for(int i=1;i<=top;i++) printf("sta[%d]=%d\n",i,sta[i]); dfsx(1,0); tot=0; for(int i=hd[1];i;i=nxt[i]) if(!vis[to[i]]) tmp[++tot]=to[i]; sort(tmp+1,tmp+tot+1,cmp2); //for(int i=1;i<=tot;i++) printf("tmp[%d]=%d\n",i,tmp[i]); int p0=1; for(int i=1;i<=top;i++) { if(!vis[bh[sta[i]]]) { vis[bh[sta[i]]]=1; //if(mid==11)printf("vis[bh[%d]=%d]=1",sta[i],bh[sta[i]]); continue; } while(p0<tot&&vis[tmp[p0]])p0++; if(vis[tmp[p0]])return 1; //if(mid==11) printf("tmp[%d]=%d fe=%d mid-dis[%d]=%lld\n",p0,tmp[p0],fe[tmp[p0]],sta[i],mid-dis[sta[i]]); if(mid-dis[sta[i]]>=fe[tmp[p0]]) { vis[tmp[p0]]=1; p0++;//vis!! if(p0>tot)return 1; } } while(p0<tot&&vis[tmp[p0]])p0++; return vis[tmp[p0]]; } int main() { //freopen("testdata.in","r",stdin); n=rdn(); for(int i=1,u,v,z;i<n;i++) { u=rdn(); v=rdn(); z=rdn(); add(u,v,z); r+=z<<1; } dfs(1,0,0); m=rdn(); for(int i=1;i<=m;i++) pos[i]=rdn(); sort(pos+1,pos+m+1,cmp); while(l<=r) { //printf("l=%lld r=%lld\n",l,r); mid=l+r>>1ll; if(solve())ans=mid,r=mid-1; else l=mid+1; } printf("%lld\n",ans); return 0; }