[2018雅礼1-2]变量 最小割

题面
考场上想到最小割竟然没想到怎么建图?!!
首先 W 就是唬人的,直接当成1 1 做最后乘上即可。
式子什么的都是唬人的,拆开计算就好,对于所有 di,ei,fi 都拆开,把每个 wi 的系数统计出来,因为最后还要加 wi 所以系数都再加个 1 ,设wi系数为 li
对于 ai,bi,ci 绝对值的项,就是当两个 w 不相等时,有个两倍的贡献,统计出wi=wj时的贡献 ri,j
限制用最小割中 inf 的边表示一下即可。
具体建图:
把每个变量看成一个点, S 向点i li 的边,点 i T li 的边,分别表示取正或负。
每对 (i,j) 之间连 ri,j 的双向边。
对于限制0, y x inf 的单向边。
对于限制1, (x,y) 之间连 inf 的双向边。
对于限制2,把 S x y T的边改成 inf
要注意的是有些边的容量可能为负,所以可以先把两条边加上一个数,最后在减去。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int inf=0x3f3f3f3f;
int n,p,q,l[510][2],r[510][510],S,T,ne[510],dl[510];
ll w;
struct edge
{
    int t,c,f;
    edge *next,*rev;
}*con[510];
void ins(int x,int y,int c)
{
    edge *p=new edge;p->t=y;p->c=c;p->f=0;p->next=con[x];con[x]=p;
    p=new edge;p->t=x;p->c=0;p->f=0;p->next=con[y];con[y]=p;
    con[x]->rev=con[y];con[y]->rev=con[x];
}
bool bfs()
{
    bool re=0;
    memset(ne,0,sizeof(ne));
    dl[1]=S;ne[S]=1;
    for(int hd=1,tl=1,v=S;hd<=tl;re|=(v==T),v=dl[++hd])
        for(edge *p=con[v];p;p=p->next)
            if(p->c>p->f&&!ne[p->t]) ne[p->t]=ne[v]+1,dl[++tl]=p->t;
    return re;      
}
int dinic(int v,int flow)
{
    if(ne[v]<0) return 0;
    if(v==T) return flow;
    int re=0;
    for(edge *p=con[v];p&&flow;p=p->next)
        if(p->c>p->f&&ne[v]+1==ne[p->t])
        {
            if(p->t==T&&min(flow,p->c-p->f)>1e9) cout<<"!!!"<<v<<endl; 
            int o=dinic(p->t,min(flow,p->c-p->f));
            p->f+=o;re+=o;
            p->rev->f-=o;flow-=o;
        }
    if(!re) ne[v]=-1;   
    return re;  
}
int main()
{
    freopen("variable.in","r",stdin);
    freopen("variable.out","w",stdout);
    int ca;
    scanf("%d",&ca);
    while(ca--)
    {
        memset(r,0,sizeof(r));
        memset(l,0,sizeof(l));
        for(int i=0;i<=n+1;i++) con[i]=NULL;
        scanf("%d%lld%d%d",&n,&w,&p,&q);
        S=0;T=n+1;
        for(int i=1;i<=p;i++)
        {
            int x,y,z,a,b,c,d,e,f;
            scanf("%d%d%d%d%d%d%d%d%d",&x,&y,&z,&a,&b,&c,&d,&e,&f);
            r[y][x]=(r[x][y]+=2*a);
            r[z][y]=(r[y][z]+=2*b);
            r[z][x]=(r[x][z]+=2*c);
            l[x][0]+=d-f;l[x][1]-=d-f;
            l[y][0]+=e-d;l[y][1]-=e-d;
            l[z][0]+=f-e;l[z][1]-=f-e;
        }
        for(int i=1;i<=n;i++) l[i][0]++,l[i][1]--;
        for(int i=1;i<=q;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if(z==0) ins(y,x,inf);
            if(z==1) ins(x,y,inf),ins(y,x,inf);
            if(z==2) l[x][0]=l[y][1]=inf;
        }
        int bs=0,ans=0;
        for(int i=1;i<=n;i++) 
        {
            int tmp=min(min(l[i][0],l[i][1]),0);
            bs+=tmp;
            ins(S,i,l[i][0]-tmp);
            ins(i,T,l[i][1]-tmp);
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(r[i][j]) ins(i,j,r[i][j]);   
        while(bfs()) ans+=dinic(S,inf);
        printf("%lld\n",(ans+bs)*w);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值