bzoj 1917: [Ctsc2010]星际旅行
Description
公元3000年,地球联盟已经攻占了银河系内的N个星球,出于资金的考虑,政府仅仅在星球间建立了N-1条双向时空隧道保证任意两个星球之间互相可达。出于管理上的考虑,第i个星球的行政长官要求每个公民在一年内不得从该星球利用时空隧道次数超过Hi次(这一统计是基于离开次数统计的,如果你已经使用从该星球离开过Hi次,那么这一年内你就不能再使用时空隧道离开这个星球了)。Louis Paosen是一个星际旅行家,他希望能使用尽量多次的时空隧道,但又不希望最终被迫定居的星球条件太过恶劣。所以他希望能知道对于每个星球i,若从0号星球出发,最终以i号星球为终点,这样的星际旅行途中最多可以使用多少次时空隧道。
solution
正解:贪心
由于题目给定 \(H[i]>du[i]\),所以至少可以遍历所有的边一个来回,然后可以证明最优情况肯定是遍历的所有边的,所以我们先遍历所有边一次,然后考虑剩余的 \(H[i]\),现在任意两点间已经连通,我们只需任意花费流量使得答案最大,所以我们把能花的都花掉,且这些花费可以算进所有点的贡献,因为已经连通.
现在的树已经成为了不存在边 \((x,y)\),\(x>0,y>0\),\(x,y\)只有一个可以大于0,我们只需考虑调整这些流量:
考虑现在在 \(x\),子节点为 \(y\).
1.当前节点\(x\)的流量>0,那么可以直接走到\(y\),使得 \(y\) 答案加\(1\)
2.\(y\) 的某个子节点 \(u\) 的流量大于0,那么可以把 \(y->x\)的流量退掉,换成 \(y->u,u->y\),使得答案加\(1\)
3.若上述情况都不存在,那么 \(y->x\) 这条边流量必须退掉,因为到达 \(x\) 之后就无法返回 \(y\)了
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
const int N=50005;
int head[N],n,num=0,val[N],nxt[N<<1],to[N<<1],tot=0,r[N];
void link(int x,int y){nxt[++num]=head[x];to[num]=y;head[x]=num;}
void dfs1(int x,int last){
int u,k;
for(int i=head[x];i;i=nxt[i]){
u=to[i];if(u==last)continue;
dfs1(u,x);
k=Min(val[x],val[u]);tot+=k<<1;
val[x]-=k;val[u]-=k;
if(val[u])r[x]=u;
}
}
int ans[N];
void dfs(int x,int last){
int u,flag;ans[x]=tot;
for(int i=head[x];i;i=nxt[i]){
u=to[i];if(u==last)continue;
if(val[x])flag=1,tot++;
else if(r[u])flag=2,tot++,val[r[u]]--;
else flag=3,tot--,val[u]++;
dfs(u,x);
if(flag==1)tot--;
else if(flag==2)tot--,val[r[u]]++;
else tot++,val[u]--;
}
}
void work()
{
int x,y;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);x++;y++;
link(x,y);link(y,x);
val[x]--;val[y]--;tot+=2;
}
dfs1(1,1);dfs(1,1);
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
}
int main()
{
work();
return 0;
}