http://codeforces.com/problemset/problem/733/F (题目链接)
题意
给出一张n个点的无向图,每一条变有两个特征值:${w,c}$;分别表示这条边的权值为${w}$,每将这条边的权值减小1需要充${c}$元钱。初始时有${S}$元钱,你可以对任意边充钱使得它的权值${w}$减少,甚至减成负数。现在需要你选出若干条边,使得所有的点都联通并且这些边的权值和在“充了钱”之后最小。
Solution
codeforces上的题目真是良心题。
想一想,假设我们已经选出了一些边,如果我们“充钱”,反正充哪条边都是只能使最终答案减小1,不如就抓着其中一条${c}$最小的边一通狂充,不充钱还想跟我玩?于是我们就很容易得到一个初始想法:跑一遍关于${w}$的最小生成树,然后狂减${c}$最小的边,得到一个初始答案${ans}$。
考虑这样一种情况:可能存在一条不在最小生成树的边,但是它的${c}$比最小生成树中的边的${c}$都要小,我们可以将它选入答案并通过对它“充钱”,使答案更优。
这样的情况怎么处理呢?我们枚举每一条不在最小生成树中并且${c}$符合要求的边,尝试将它加入生成树中。加入一条边${(u,v)}$,那么必定要删掉树上一条边,那么删去的这条边一定是树上${u,v}$间${w}$最大的一条边,此时我们不用考虑树中的边的${c}$,因为如果我们要“充钱”一定是对新加入的这条边充,如果最后剩下的钱小于新加入这条边的${c}$,那么显然剩下的钱已经不能再做出贡献了。
那么现在问题就是如何快速查询树上两点间的最长边,是不是很熟悉,没错就是NOIP2013货车运输,直接树上倍增即可。
细节
记得开long long。然后方案的统计有点麻烦,注意不要写错,Wa on test 100,莫名其妙过了前面99个点,这是要FST的节奏啊→_→。
代码
// codeforces 733F #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define MOD 99999997 #define inf 2000000000 #define Pi acos(-1.0) #define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout); using namespace std; const int maxn=200010; struct edge {int to,next,w,id;}e[maxn<<1]; struct data {int u,v,w,c,id;}a[maxn]; int f[maxn],head[maxn],fa[maxn][30],bin[30],d[maxn][30],deep[maxn],b[maxn]; int cnt,S,n,m,prt[maxn],pp[maxn]; bool cmp(data a,data b) { return a.w<b.w; } int maxw(int x,int y) { return a[x].w<a[y].w ? y : x; } int find(int x) { return x==f[x] ? x : f[x]=find(f[x]); } void link(int u,int v,int w) { e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w; e[++cnt].to=u;e[cnt].next=head[v];head[v]=cnt;e[cnt].w=w; } void dfs(int x) { for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int i=1;i<=20;i++) d[x][i]=maxw(d[x][i-1],d[fa[x][i-1]][i-1]); for (int i=head[x];i;i=e[i].next) if (e[i].to!=fa[x][0]) { deep[e[i].to]=deep[x]+1; d[e[i].to][0]=e[i].w; fa[e[i].to][0]=x; dfs(e[i].to); } } int lca(int x,int y) { if (deep[x]<deep[y]) swap(x,y); int t=deep[x]-deep[y]; for (int i=0;bin[i]<=t;i++) if (bin[i]&t) x=fa[x][i]; for (int i=20;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return x==y ? x : fa[x][0]; } int query(int x,int f) { int t=deep[x]-deep[f]; int res=0; for (int i=0;bin[i]<=t;i++) if (bin[i]&t) res=maxw(res,d[x][i]),x=fa[x][i]; return res; } int main() { bin[0]=1;for (int i=1;i<=20;i++) bin[i]=bin[i-1]<<1; scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) scanf("%d",&a[i].w); for (int i=1;i<=m;i++) scanf("%d",&a[i].c); for (int i=1;i<=m;i++) scanf("%d%d",&a[i].u,&a[i].v),a[i].id=i; scanf("%d",&S); sort(a+1,a+1+m,cmp); for (int i=1;i<=n;i++) f[i]=i; int C=inf; LL ans=0,tot=0;int num=0,dl=0; for (int i=1;i<=m;i++) { int r1=find(a[i].u),r2=find(a[i].v); if (r1!=r2) { link(a[i].u,a[i].v,i); f[r1]=r2; b[i]=1; tot+=(LL)a[i].w; if (a[i].c<C) C=a[i].c,num=i; } } ans=tot-S/C; dfs(1); for (int i=1;i<=m;i++) if (a[i].c<C) { int ff=lca(a[i].u,a[i].v); int x=maxw(query(a[i].u,ff),query(a[i].v,ff)); LL tmp=(LL)tot-a[x].w+a[i].w-S/a[i].c; if (ans>tmp) { ans=tmp;b[dl]=1;b[num]=0;b[dl=x]=0;num=i;b[i]=1;} } printf("%lld ",ans); for (int i=1;i<=m;i++) if (b[i]) { int x=num==i ? S/a[i].c : 0; prt[a[i].id]=a[i].w-x; pp[a[i].id]=1; } for (int i=1;i<=m;i++) if (pp[i]) printf("%d %d ",i,prt[i]); return 0; }