肝了一个下午,终于把这个绝世好题写完了(滑稽)
满分做法:
看到题目求最短时间,说明更高的时间也可以控制,满足答案单调性,可以二分;(技巧)
看到这些比较复杂的题目,一定要滤清自己该干什么,不要慌!!!
显然一个军队最后停留的节点深度越小,它控制的叶子结点越多。所以我们尽量让军队往上走,如果过程中它达到了时间限制,那么就把当前节点打上驻扎标记。
若一个军队可以走到根节点,我们让他暂停。我们记录它到达根节点后剩余的时间,可以用二元组去存储。整个过程可以用倍增去解决。
void dfs(int x)//倍增预处理 { for(int p=last[x];p;p=pre[p]) { int v=other[p]; if(v==jump[x][0]) continue; jump[v][0]=x; dis[v][0]=len[p]; dfs(v); } } bool sta[50007];//该节点是否驻扎 pair<ll,int> h[50007];//二元组记录上移后闲置军队,second为上移后的节点 ,first为剩余时间 int ctot=0; for(int i=1;i<=m;i++) { int x=id[i];//军队位置 ll cnt=0;//上移时间 for(int j=19;j>=0;j--)//倍增 { if(jump[x][j]>1&&cnt+dis[x][j]<=limit) { cnt+=dis[x][j]; x=jump[x][j]; } } if(jump[x][0]==1&&dis[x][0]+cnt<=limit) { h[++ctot]=make_pair(limit-dis[x][0]-cnt,x); } else sta[x]=1; }
接下来判断需要驻扎的节点(与根节点直接相连),直接向下dfs即可,如果遇到有驻扎的地方就返回1。如果到叶子结点时,叶子没有驻扎
就返回0。
bool dfs1(int x) { bool pson=0;//判断有没有儿子 if(sta[x]) return 1; for(int p=last[x];p;p=pre[p]) { int v=other[p]; if(v==jump[x][0]) continue; pson=1; if(!dfs1(v)) return 0; } if(!pson) return 0;//如果是叶子结点且无驻扎 return 1; } bool need[50007];//此节点需不需要驻扎 for(int p=last[1];p;p=pre[p]) { int v=other[p]; if(!dfs1(v)) need[v]=1; }
之后,该对需要驻扎的节点进行初步处理。这里用到了贪心,对于任意一个需要被驻扎的节点,若它上面停留着一支军队不能到达根节点并返回该节点,
则令其驻扎在该节点;另外的,因为一个节点只需要一支军队驻扎,因此我们在这里选择剩余时间最小的节点,对二元组排序即可。为什么呢?因为到达根节点
后,可以归并为同一种状态,都是从根节点向子节点走剩余的时间。因为排序后,后面的点的剩余时间大于该点,但和此点到根节点边权的大小就不得而知。但是
我可以用较小的剩余时间去驻扎,何乐而不为呢!
sort(h+1,h+ctot+1); int atot=0; for(int i=1;i<=ctot;i++) { if(need[h[i].second]&&h[i].first<dis[h[i].second][0])////若该军队所处的节点需要被驻扎且该军队无法到达根节点并返回 need[h[i].second]=0; else tim[++atot]=h[i].first; }
下一步找到仍需要驻扎的节点,直接储存边权即可
//找到仍需驻扎的节点 int btot=0; for(int p=last[1];p;p=pre[p]) { int v=other[p]; if(need[v]) ned[++btot]=dis[v][0];//储存从根节点到它的时间 }
最后就是贪心匹配最终结果,排序,对于现在闲置的军队和需要被驻扎的节点,让剩余时间小的军队优先驻扎在距离根节点近的节点,这样可以保证决策最优。
if(atot<btot)//如果剩余的军队比需要被驻扎的节点还少,则不能成功 return 0; sort(tim+1,tim+atot+1); sort(ned+1,ned+btot+1); int h=1,h1=1; while(h<=atot&&h1<=btot)//贪心,用剩余时间少的去覆盖 { if(tim[h]>=ned[h1]) { h1++; h++; } else h++; } if(h1>btot) return 1; else return 0;
这样这道题就轻松解决了(ε=(´ο`*)))唉)
附上完整代码
#include<queue> #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> #include<iostream> using namespace std; const int maxm=100007; typedef long long ll; int n,m; int id[50007]; int pre[maxm],last[50007],other[maxm],tot,len[maxm]; int jump[50007][21]; ll l,r,mid,ans; ll dis[50007][21];//倍增长度 bool sta[50007];//该节点是否驻扎 pair<ll,int> h[50007];//二元组记录上移后闲置军队,second为上移后的节点 ,first为剩余时间 ll tim[50007];//储存军队的剩余时间 bool need[50007]; ll ned[50007]; void add(int x,int y,int z) { tot++; pre[tot]=last[x]; last[x]=tot; other[tot]=y; len[tot]=z; } void dfs(int x) { for(int p=last[x];p;p=pre[p]) { int v=other[p]; if(v==jump[x][0]) continue; jump[v][0]=x; dis[v][0]=len[p]; dfs(v); } } bool dfs1(int x) { bool pson=0; if(sta[x]) return 1; for(int p=last[x];p;p=pre[p]) { int v=other[p]; if(v==jump[x][0]) continue; pson=1; if(!dfs1(v)) return 0; } if(!pson) return 0; return 1; } bool check(ll limit) { //上移 memset(sta,0,sizeof(sta)); memset(tim,0,sizeof(tim)); memset(ned,0,sizeof(ned)); memset(h,0,sizeof(h)); memset(need,0,sizeof(need)); int ctot=0; for(int i=1;i<=m;i++) { int x=id[i]; ll cnt=0;//上移时间 for(int j=19;j>=0;j--)//倍增 { if(jump[x][j]>1&&cnt+dis[x][j]<=limit) { cnt+=dis[x][j]; x=jump[x][j]; } } if(jump[x][0]==1&&dis[x][0]+cnt<=limit) { h[++ctot]=make_pair(limit-dis[x][0]-cnt,x); } else sta[x]=1; } //寻找需要驻扎的结点 for(int p=last[1];p;p=pre[p]) { int v=other[p]; if(!dfs1(v)) need[v]=1; } //对需要驻扎的进行初步处理 sort(h+1,h+ctot+1); int atot=0; for(int i=1;i<=ctot;i++) { if(need[h[i].second]&&h[i].first<dis[h[i].second][0])////若该军队所处的节点需要被驻扎且该军队无法到达根节点并返回 need[h[i].second]=0; else tim[++atot]=h[i].first; } //找到仍需驻扎的节点 int btot=0; for(int p=last[1];p;p=pre[p]) { int v=other[p]; if(need[v]) ned[++btot]=dis[v][0];//储存从根节点到它的时间 } if(atot<btot)//如果剩余的军队比需要被驻扎的节点还少,则不能成功 return 0; sort(tim+1,tim+atot+1); sort(ned+1,ned+btot+1); int h=1,h1=1; while(h<=atot&&h1<=btot)//贪心,用剩余时间少的去覆盖 { if(tim[h]>=ned[h1]) { h1++; h++; } else h++; } if(h1>btot) return 1; else return 0; } int main() { scanf("%d",&n); bool flag=0; for(int i=1;i<=n-1;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); r+=z; } dfs(1); for(int j=1;j<=19;j++) { for(int i=1;i<=n;i++) { jump[i][j]=jump[jump[i][j-1]][j-1]; dis[i][j]=dis[i][j-1]+dis[jump[i][j-1]][j-1]; } } scanf("%d",&m); for(int i=1;i<=m;i++) scanf("%d",id+i); ll l=0; while(l<=r) { mid=(l+r)>>1; if(check(mid)) { ans=mid; r=mid-1; flag=1; } else l=mid+1; } if(!flag) cout<<"-1"<<endl; else printf("%lld ",ans); return 0; }