P1084 疫情控制
这题是真恶心 5h的艰辛。。。
题意:
H 国有 n个城市,这 n 个城市用n−1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。
H国的首都爆发了一种危害性极高的传染病。
当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。
但特别要注意的是,首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。
一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。
注意:不同的军队可以同时移动。
暴力30 QAQ
1、越是靠近根节点,能控制的节点就越多(贪心),所以,这一段可以用倍增优化
f[i][j]代表从i向上跳$2^j$步所到达的点
dis[i][j]表示从i向上跳$2^j$走的距离(距离=时间)
2、对于本题,要求最少用时,而最少用时取决于最大的时间----->让最大值最小------->二分答案
详见代码
#include<cstdio> #include<iostream> #include<cstring> #include<cctype> #include<algorithm> using namespace std; #define int long long #define olinr return #define _ 0 #define love_nmr 0 #define DB double #define nmr 50505 int n; int m; int head[nmr]; int cnt; int ans; int far[nmr]; //far[i]表示i所在子树中,距离根最远的并且能到根的军队 int f[nmr][35]; //f[i][j]记录i向上跳1<<j步所到达的点 int dis[nmr][35]; //f[i][j]记录i向上跳1<<j步经过的距离 struct node { int to; int nxt; int dis; }; node edge[nmr<<1]; //链式前向星 int pp[nmr]; //军队初始所在地 bool vis[nmr]; //判断i是否驻扎军队 struct wmy { int dis; int id; //id记录的是点 friend bool operator < (const wmy &a,const wmy &b) { return a.dis>b.dis; } }; wmy lft[nmr]; //在当前二分答案枚举的mid的限制下,军队id到根节点还能余下多少时间 wmy jl[nmr]; //以id为根的子树(1的孩子)有未被控制的叶子,则记录这个孩子与1的距离 int jijl; int jilft; int use[nmr]; //判断lft中的(在其中说明可以支援,因为有剩余时间)军队是否已经去支援某地了(不能再用了) int leftmin[nmr]; //用于far的统计,找到最远的能到根的点,记录的是以i为根的子树最远军队的距离 inline int read() { int x=0,f=1; char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') f=-f; ch=getchar(); } while(isdigit(ch)) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } inline void put(int x) { if(x<0) { x=-x; putchar('-'); } if(x>9) put(x/10); putchar(x%10+'0'); } inline void add(int from,int to,int dis) //链式前向星加边 { cnt++; edge[cnt].to=to; edge[cnt].dis=dis; edge[cnt].nxt=head[from]; head[from]=cnt; } inline void dfs(int x,int fa) //处理出倍增的f和dis { f[x][0]=fa; for(int i=head[x];i;i=edge[i].nxt) { int go=edge[i].to; if(go!=fa) { dis[go][0]=edge[i].dis; dfs(go,x); } } } inline void beizeng() //倍增预处理 { dfs(1,0); for(int j=1;j<=26;j++) for(int i=1;i<=n;i++) { f[i][j]=f[f[i][j-1]][j-1]; dis[i][j]=dis[i][j-1]+dis[f[i][j-1]][j-1]; } } inline bool dfs_if_ok(int x,int fa) { bool leaf=1; //是否为叶子 bool bj=1; //孩子没被控制,回溯时false if(vis[x]) return true; //此节点(以此节点为根子树)已经被控制 for(int i=head[x];i;i=edge[i].nxt) { int go=edge[i].to; if(go!=fa) { leaf=0; //有儿子=不是叶子 if(!dfs_if_ok(go,x)) //向儿子找,如果有没被控制的 { bj=0; //标记清空,回溯时会知道有没被控制的 if(x==1) //恰好为根的儿子 { jl[++jijl].id=go; //记录这个孩子有没被控制的叶子 jl[jijl].dis=edge[i].dis; //记录控制想控制它的最近距离(因为go子树内的军队是控制不了这个叶子的,因为贪心,我们已经让其内军队控制了最多的节点,所以只能等待别的子树来救援了,而最近的当然是根的儿子啦 } else return false; //等同于return bj,回溯告诉父亲自己有调皮的叶子QAQ } } } if(leaf) return false; //是叶子。。。如果不能搜到叶子,说明在if(vis[x]) return true;已经返回了,既然能搜到,说明此叶子存在一条到根的没有军队的通路 return bj; //返回标记 } inline bool ok(int limit) //二分答案的判断,看在limit 的限制下,能否成立 { jijl=0; //这两个是用于记录jl和lft的 jilft=0; memset(jl,0,sizeof jl); memset(lft,0,sizeof lft); memset(use,0,sizeof use); memset(vis,0,sizeof vis);//初始化不能少!! memset(far,0,sizeof far); for(int i=1;i<=m;i++) //枚举军队,贪心把他们使劲往上提 { int now=pp[i]; //now是当前军队 int tot=0; //统计距离判断限制 for(int j=25;j>=0;j--) { if(f[now][j]>1&&tot+dis[now][j]<=limit) //跳不到根并且没超限制 { tot+=dis[now][j]; //先统计距离,再往上跳(表示在此栽了将近1h) now=f[now][j]; } } if(f[now][0]==1&&tot+dis[now][0]<=limit) //成功在限制之内跳到了根的孩子的位置(so good),并且还有能力跳到根!(说明有支援蒟蒻的潜能) { lft[++jilft].id=i; //放到支援团里 lft[jilft].dis=limit-tot-dis[now][0]; //记录他们的支援能力(当前限制下的剩余时间) if(!far[now]||lft[jilft].dis<leftmin[now]) //找到农村的军队(最远的) { far[now]=i; //记录能到跟最远的是i leftmin[now]=lft[jilft].dis; //更新最远距离(只为far记录用) } } else vis[now]=1; //蒟蒻表示连根都到不了,太远了,所以,他要把他的贡献最大化,根据贪心思想,越靠上,控制的越多,所以能跳掉的最高点,就在那里,贡献是最大的 } if(dfs_if_ok(1,0)) return true; //判断是否所有点已经被控制 sort(lft+1,lft+jilft+1); //按救援能力(剩余时间)从大到小排序 sort(jl+1,jl+jijl+1); //按最近距离从大到小排序 int now=1; //开始分配援军(now 当前援军) use[0]=true; //!!!必须有!!! 如果1的某一儿子内没有军队,那么在下面第三行判断时就是use[0]如果在开始不把它变成true,他会直接认为不存在的军队0支援了它,于是跳过了。。。 for(int i=1;i<=jijl;i++) { if(!use[far[jl[i].id]]) //最远能到根的点,估计累坏了吧QAQ,让它支援自己这棵树就行了(自己的树没管好还管别人。) { use[far[jl[i].id]]=true; //标记已使用这个军队 continue; //已经有援军了,继续下一个蒟蒻 } while(now<=jilft&&(use[lft[now].id]||lft[now].dis<jl[i].dis)) now++; //还有援军(<--前提)并且当前军队用过,或者对于此蒟蒻,当前援军表示无法支援,则向下一个援军求助 if(now>jilft) return false; //援军没了,还有几个蓝瘦香菇的蒟蒻QAQ use[lft[now].id]=true; //蒟蒻找到了援军,援军去支援了,不能再帮别人了,给他标记 } return true; //以上全成立,所以ok } signed main() { n=read(); for(int i=1;i<n;i++) { int x=read(); int y=read(); int z=read(); add(x,y,z); //连边 add(y,x,z); } beizeng(); //倍增预处理 m=read(); for(int i=1;i<=m;i++) pp[i]=read(); int l=0,r=nmr<<10; ans=-1; //无解是-1!!!! while(l<=r) //二分答案 { int mid=(l+r)>>1; if(ok(mid)) { ans=mid; r=mid-1; } else l=mid+1; } put(ans); olinr ~~(0^_^0)+love_nmr; }
----olinr