首页 > 其他 > 详细

2019暑假集训DAY4(problem3.construct)(网络流)

时间:2019-07-13 22:58:20      阅读:120      评论:0      收藏:0      [点我收藏+]

改编自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

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!