改编自BZOJ2521 [Shoi2010]最小生成树
题面
1 construct
1.1 Description
Me懒得编题面了
给出一个n个点m条边的图,每条边有边长。现在我们钦定了一条边,要使该边一定出现在最小生成树中
你可以进行如下操作:将某条边的长度+1,并付出1的代价
最少需要付出多少代价可以满足要求呢?
1.2 Input
第一行三个整数 ,分别表示点数,边数,被钦定的边的编号
接下来 行,每行三个整数 ,表示一条连接 的路径,其长度为
1.3 Output
输出一行一个数字,表示最少操作次数
1.4 Sample Input
4 6 1 1 2 2 1 3 2 1 4 3 2 3 2 2 4 4 3 4 5 |
1.5 Sample output
1 |
解释:将1放在中间,2 , 3 , 4号点放在1的周围可以画出此图。容易发现,对连接2,3的边进行一次操作即可满足要求
1.6 Note
对于全部数据,满足 ,数据有梯度
保证有 20% 的数据(均匀分布),满足1<=n<=500,1<=M<=800,1<=d<=1e6
保证有 20% 的数据(均匀分布),满足编号为 的边是一个桥(由于一些特殊原因,保证保证不存在)
其实这道题暴力可以过(没想到吧?可能是wys大佬的数据原因)
我们可以先求出最小生成树(设钦定边的边权为L)
如果钦定的边在树中则不需要付出代价(注意:分析样例发现,可能存在和钦定边相同大小的非树边可以代替它,所以在排序的时候可以做一点小处理,在边权相等的边中,把钦定边往后丢)
如果钦定的边不在树中,则可以借鉴求次小生成树的做法,把钦定边丢进树中,则会形成一个环,在这个环中,试着是每一条边(除了钦定边)增大好使得使用钦定边,删掉原树边。但这时我们会发现一个问题,可能增大了某条树边后(边权变为L+1),存在在钦定边的前面还有一条更小的可以加入树中。对此,我们可以维护一个并查集,对于要删去的树边,删去后会分成两个连通块,我们判断在钦定边前面都没有这样的一条边,使得两个块可以连接起来(自己画图吧!这里不想再画了rua)。具体怎么操作呢?暴力就可以了,每次都把并查集清空,又加入两个块中的所有的边,判断时看father是不是同一个就行了,如果存在这样的边就把这条边的边权加到L+1,然后重复操作(我们可以看到n的范围是500)。
这里贴出mwy的代码(因为我没打)
#include<bits/stdc++.h> using namespace std; #define N 505 #define M 805 int n,m,k,ans=0x7f7f7f,fa1[N],fa2[N],head[N],nex[N<<1],w[N<<1],to[N<<1],tot=0,f[N]; int dep[N],val; struct node{ int x,y,w,o,val; }ap[M]; bool cmp(const node a,const node b){ if(a.w==b.w) return a.val>b.val; return a.w<b.w; } void add(int a,int b,int ww) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww; } int get1(int x) { if(x==fa1[x]) return x; return fa1[x]=get1(fa1[x]); } int get2(int x) { if(x==fa2[x]) return x; return fa2[x]=get2(fa2[x]); } void dfs1(int u,int father) { for(int i=head[u];i;i=nex[i]){ int v=to[i]; if(v==father) continue; f[v]=u; dep[v]=dep[u]+1; dfs1(v,u); } } void dfs2(int u,int father,int fl,int x) { for(int i=head[u];i;i=nex[i]){ int v=to[i]; if(v==father) continue; if(fl&&v==x) continue; int f1=get2(u),f2=get2(v); if(f1!=f2) fa2[f1]=f2; dfs2(v,u,fl,x); } } int work(int a,int b) { int sum=0; for(int i=1;i<=n;i++) fa2[i]=i; dfs2(a,f[a],0,0),dfs2(b,0,1,a);// for(int i=1;i<=m;i++){ if(ap[i].o==k) break; int f1=get2(ap[i].x),f2=get2(ap[i].y); if(f1!=f2) sum+=(val+1-ap[i].w); } return sum; } void lca(int a,int b) { if(dep[a]<dep[b]) swap(a,b); while(dep[f[a]]>=dep[b]){ ans=min(ans,work(a,b)); a=f[a]; } if(a==b) { printf("%d\n",ans); return ; } while(f[a]!=f[b]){ ans=min(ans,work(a,b)); a=f[a];//direction ans=min(ans,work(b,a)); b=f[b]; }/**/ ans=min(ans,work(a,b));// ans=min(ans,work(b,a)); printf("%d\n",ans); } int main() { freopen("construct.in","r",stdin); freopen("construct.out","w",stdout); int x,y,fll=0; scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) scanf("%d%d%d",&ap[i].x,&ap[i].y,&ap[i].w),ap[i].o=i; ap[k].val=-1; x=ap[k].x,y=ap[k].y; val=ap[k].w; sort(ap+1,ap+1+m,cmp); int cnt=0; for(int i=1;i<=n;i++) fa1[i]=i; for(int i=1;i<=m;i++){ int f1=get1(ap[i].x); int f2=get1(ap[i].y); if(f1!=f2){ if(ap[i].o==k){ fll=1; break; } fa1[f1]=f2; add(ap[i].x,ap[i].y,ap[i].w),add(ap[i].y,ap[i].x,ap[i].w); cnt++; } if(cnt==n-1) break; } if(fll) { printf("0\n"); return 0; } f[1]=0; dep[1]=1; dfs1(1,0); lca(x,y); return 0; } /* 8 18 2 6 1 46 3 2 63 7 3 33 5 6 66 4 7 65 5 4 61 8 5 33 7 8 33 4 2 27 1 7 23 5 8 10 6 8 41 2 3 91 1 4 72 7 1 89 4 8 16 3 5 11 2 8 77 9 10 2 3 6 88 7 8 100 5 6 87 6 8 1 1 3 2 2 5 3 5 9 4 2 4 3 7 4 2 1 2 1 */
但其实这道题的正解是一个网络流(没想到吧?)
让我们一起来分析一下它为什么是个网络流。
我们发现,对于钦定边,只有比它小的可能会对其造成影响。而对于这些边,我们可以通过付出一些代价使得对钦定边不造成影响。
或者我们可以这么想,对钦定边造成影响等价于什么呢?是不是没有连钦定边(端点为u,v)的时候,因为连了其他的一些边,使得u和v连通了。(right!)
于是我们可以设u和v分别为源点S和汇点T,对于小于等于钦定边权值的边,就以它们的权值差+1作为流量,求最小割(因为要使得这些边无法让S和T连通)
这种思想可谓是十分奇妙了~贴出代码
#include<bits/stdc++.h> #define INF 2100000001 #define M 300003 #define N 100003 #define LL long long using namespace std; int read() { int f=1,x=0;char s=getchar(); while(s<‘0‘||s>‘9‘){if(s==‘-‘)f=-1;s=getchar();} while(s>=‘0‘&&s<=‘9‘){x=x*10+s-‘0‘;s=getchar();} return x*f; } struct EDGE{ int x,y,z,flagg; }w[M]; struct edgee{ int to,nextt,val; }e[M]; int head[N],d[N],cur[N]; int tot=1; int S,T; void add(int a,int b,int v) { tot++; e[tot].to=b; e[tot].nextt=head[a]; e[tot].val=v; head[a]=tot; tot++; e[tot].to=a; e[tot].nextt=head[b]; e[tot].val=0; head[b]=tot; } queue<int>q; int bfs() { memset(d,0,sizeof(d)); while(!q.empty())q.pop(); q.push(S);d[S]=1; while(!q.empty()) { int x=q.front();q.pop(); for(int i=head[x];i;i=e[i].nextt) { int v=e[i].to; if(!d[v]&&e[i].val) { d[v]=d[x]+1; q.push(v); if(v==T) return 1; } } } return 0; } int dinic(int x,int flow) { if(x==T) return flow; int rest=flow; int k; for(int i=head[x];i;i=e[i].nextt) { int v=e[i].to; if(e[i].val&&d[v]==d[x]+1) { k=dinic(v,min(rest,e[i].val)); if(!k) d[v]=0; e[i].val-=k; e[i^1].val+=k;rest-=k; } } return flow-rest; } int main() { freopen("construct.in","r",stdin); freopen("construct.out","w",stdout); int n=read(),m=read(),id=read(); for(int i=1;i<=m;++i) { w[i].x=read();w[i].y=read();w[i].z=read(); } for(int i=1;i<=m;++i) { if(i==id)S=w[i].x,T=w[i].y; else if(w[i].z<=w[id].z) add(w[i].x,w[i].y,w[id].z-w[i].z+1),add(w[i].y,w[i].x,w[id].z-w[i].z+1); } int flow=0;LL maxflow=0; while(bfs()) { while(flow=dinic(S,INF)) maxflow+=flow; } printf("%lld\n",maxflow); } /* 4 6 1 1 2 2 1 3 2 1 4 3 2 3 2 2 4 4 3 4 53 4 53 4 53 4 53 */
2019暑假集训DAY4(problem3.construct)(网络流)
原文:https://www.cnblogs.com/yyys-/p/11182213.html