【集训队互测2013】供电网络

Description

给出一个n个点,m条边的有向图,每个点初始的值有正有负。你可以在每一个点加上一个数或是减去一个数,代价为你所选择的数*你所选择的点的加或减的费用(加减费用可以不同)。你也可以通过一条边把一定大小的数从起点“运”到终点,设为所运输数为x,则代价为 ax2+bx 。a,b为这条边的基础属性。且每条边有运输上界r和下界l,求把所有点的数都变为0的最小代价。
n <= 200, m <= 600, 1 <= a, b <=3, 1 <= l<= 10, 1 <= r<=100。

Solution

很明显的网络流模型。从原点向每个点连上界为∞,下界为0,费用为它加一个数的代价的边。表示你可以任意的加上一个数。从每个点向汇点连上界为∞,下界为0,费用为它减一个数的代价的边。表示你可以任意的减少一个数。然后对于每一个正权点,从原点向它连上界和下界都为它的权值,费用为0的边,表示你这个点之前就有这么大的数。对于每一个负权点,从它向汇点连上界和下界都为它的权值的相反数,费用为0的边,表示你还需要这么大的数。对于每一条边,由于它是二次的,于是我们可以把它拆成一堆边,每条边的上界为1,下界为0。即 a+b,3a+b,5a+b... 这样就能表示所有情况了。最后再跑一遍上下界最小费用可行流就行了。注意,由于这样拆边会导致边数过大,于是我们可以使用动态加边大法。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define inf 1000000
#define ll long long
#define N 205
#define M 150500
using namespace std;
ll ans;
int n,m,x,y,z,j,k,l,S,T,SS,TT,used[N],flag[N][N];
int t[M],f[M],v[M],next[M],last[N],dis[N],bz[N],d[M],p[N];
void add(int x,int y,int z,int k) {
    t[++l]=y;f[l]=z;v[l]=k;next[l]=last[x];last[x]=l;
    t[++l]=x;v[l]=-k;next[l]=last[y];last[y]=l;
}
void link(int x,int y,int low,int up,int cost) {
    add(x,y,up-low,cost);
    if (low) used[y]+=low,used[x]-=low,ans+=cost*low;
}
struct note{
    int x,y,a,b,l,r;
}a[605];
bool spfa() {
    fo(i,1,m)
        if (flag[a[i].x][a[i].y]==a[i].l&&a[i].l<a[i].r) {
            a[i].l++;add(a[i].x,a[i].y,1,
            a[i].a*(2*a[i].l-1)+a[i].b);
        }
    memset(dis,127,sizeof(dis));int mx=dis[SS];dis[SS]=0;
    memset(bz,0,sizeof(bz));bz[SS]=1;
    int i=0,j=1;d[1]=SS;
    while (i<j) {
        rep(k,d[++i])
            if (f[k]&&dis[t[k]]>dis[d[i]]+v[k]) {
                dis[t[k]]=dis[d[i]]+v[k];p[t[k]]=k;
                if (!bz[t[k]]) bz[t[k]]=1,d[++j]=t[k];
            }
        bz[d[i]]=0;
    }
    return dis[TT]!=mx;
}
void find() {
    int i=TT;
    while (i!=SS) {
        x=t[p[i]^1];y=t[p[i]];
        f[p[i]]--;f[p[i]^1]++;
        flag[x][y]++;flag[y][x]--;
        ans+=v[p[i]];i=x;
    }
}
int main() {
    scanf("%d%d",&n,&m);S=0,T=n+1;SS=n+2;TT=n+3;l=1;
    fo(i,1,n) {
        scanf("%d%d%d",&x,&y,&z);
        if (x>0) link(S,i,x,x,0);else link(i,T,-x,-x,0);
        link(S,i,0,inf,y);link(i,T,0,inf,z);
    }
    fo(i,1,m) {
        scanf("%d%d%d%d%d%d",&a[i].x,&a[i].y,
        &a[i].a,&a[i].b,&a[i].l,&a[i].r);
        link(a[i].x,a[i].y,a[i].l,a[i].l,0);
        ans+=a[i].l*a[i].l*a[i].a+a[i].l*a[i].b;
        flag[a[i].x][a[i].y]+=a[i].l;
        flag[a[i].y][a[i].x]-=a[i].l;
    }
    link(T,S,0,inf,0);
    fo(i,S,T) if (used[i]>0) link(SS,i,0,used[i],0);
    else link(i,TT,0,-used[i],0);
    while (spfa()) find();
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值