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

题目

题目大意

给你一个有向图,每个点开始有一定的水量(可能为负数),可以通过边流到其它点。
每条边的流量是有上下界的。
每个点的水量可以增加或减少(从外界补充或泄出到外界),但是需要费用,和增加(减少)流量呈正比例函数关系。
每条边的流量也需要费用,费用和流量呈二次函数关系(常数项为\(0\))。
问将所有水流完的最小花费。


思考历程

这显然是一道上下界最小费用可行流嘛!
==有源有汇的上下界可行流的做法:建立超级源\(ss\)和超级汇\(tt\)(和\(S\)、\(T\)),对于\(u\)到\(v\)的容量为\([low,up]\)的边。这时从\(ss\)到\(v\)连一条容量为\(low\)的边,\(u\)到\(tt\)连一条容量为\(low\)的边,\(u\)到\(v\)连一条\(up-low\)的边。并且要从\(T\)到\(S\)连一条容量为无限大的边。==
具体原因不再赘述。
对于每个点的初始水量,如果为正数,就从\(S\)向它连一条容量上下界都为水量的边,费用为\(0\)。
如果为负数,就从\(T\)向它连一条容量上下界都为水量的绝对值的边,费用为\(0\)。
对于每个点和外界之间的关系,可以从\(S\)到它连一条上限为无限大的边,它到\(T\)连一条上限为无限大的边,费用由题目给定。
可是最麻烦的来了,这个二次函数该怎么处理呢?
想不出来……
最终交了个错误的程序上去,成功爆0……


正解

前面的都差不多了,就只有二次函数的那一部分。
二次函数为\(y=ax^2+bx\),直接搞似乎不行,那就试着将它们拆开,变成\(a+b\)、\(3a+b\)、\(5a+b\)……这些边。每条边的容量为\(1\)。
具体来说,当\(x\)变成\(x+1\)时,费用就会新增\(a(2x+1)+b\)
由于我们跑的是最小费用可行流,一定会先跑更小的边。所以这种方法是不可能WA的。
但是如果直接这样拆,边会很多啊!想一想,要拆成最多\(100\)条边……
于是就有了动态加边大法!
一开始只加入\(a+b\)的边,如果这条边被流满,那就加一条\(3a+b\)的边,以此类推……
当然,由于上下界的问题,一开始加的并不是\(a+b\)。先将最低费用\(a*low^2+b*low\)加入答案。为了使费用不重复计算,\(ss\)到\(v\)和\(u\)到\(tt\)的边就不需要费用了,只是中间那条\(u\)到\(v\)的边初始费用变成了\(a(2low+1)+b\)。
至此整道题就解决了。


代码

独爱zkw费用流……

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define min(a,b) ((a)<(b)?(a):(b))
#define INF 1000000000
#define N 1000
int n,m;
struct EDGE{
    int to,c,w;
    EDGE *las;
    int sp;
} e[100000];
int ne;
EDGE *last[N];
#define rev(ei) (e+(int((ei)-e)^1))
inline void link(int u,int v,int c,int w,int sp=0){
    e[ne]={v,c,w,last[u],sp};
    last[u]=e+ne++;
    e[ne]={u,0,-w,last[v],0};
    last[v]=e+ne++;
}
int nsp;
int mx[100000],ad[100000];
int ex[100000];
inline void link2(int u,int v,int low,int up,int a,int b){
    nsp++;
    mx[nsp]=up,ad[nsp]=a*2,ex[nsp]=low+1;
    e[ne]={v,1,a*(2*low+1)+b,last[u],nsp};
    last[u]=e+ne++;
    e[ne]={u,0,-(a*(2*low+1)+b),last[v],0};
    last[v]=e+ne++;
}
int s,t,ss,tt;
int mincost;
int dis[N];
int vis[N],BZ;
int dfs(int x,int s){
    if (x==tt){
        mincost+=dis[ss]*s;
        return s;
    }
    int have=s;
    vis[x]=BZ;
    for (EDGE *ei=last[x];ei;ei=ei->las)
        if (vis[ei->to]!=BZ && ei->c && dis[x]==dis[ei->to]+ei->w){
            int t=dfs(ei->to,min(have,ei->c));
            ei->c-=t,rev(ei)->c+=t,have-=t;
            if (ei->sp && ex[ei->sp]<mx[ei->sp]){
                link(x,ei->to,1,ei->w+ad[ei->sp],ei->sp);
                ex[ei->sp]++;
                ei->sp=0;
            }
            if (!have)
                return s;
        }
    return s-have;
}
inline bool change(){
    int d=INF;
    for (int i=1;i<=n+4;++i)
        if (vis[i]==BZ)
            for (EDGE *ei=last[i];ei;ei=ei->las)
                if (vis[ei->to]!=BZ && ei->c)
                    d=min(d,dis[ei->to]+ei->w-dis[i]);
    if (d==INF)
        return 0;
    for (int i=1;i<=n+4;++i)
        if (vis[i]==BZ)
            dis[i]+=d;
    return 1;
}
inline void zkw(){
    do
        do
            BZ++;
        while (dfs(ss,INF));
    while (change());
}
int main(){
    scanf("%d%d",&n,&m);
    s=n+1,t=n+2,ss=n+3,tt=n+4;
    for (int i=1;i<=n;++i){
        int left,in,out;
        scanf("%d%d%d",&left,&in,&out);
        if (left>0)
            link(ss,i,left,0),link(s,tt,left,0);
        else if (left<0)
            link(ss,t,-left,0),link(i,tt,-left,0);
        link(s,i,INF,in);
        link(i,t,INF,out);
    }
    link(t,s,INF,0);
    for (int i=1;i<=m;++i){
        int u,v,a,b,low,up;
        scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&low,&up);
        mincost+=a*low*low+b*low;
        if (a){
            if (low)
                link(ss,v,low,0),link(u,tt,low,0);
            if (up-low)
                link2(u,v,low,up,a,b);
        }
        else{
            if (low)
                link(ss,v,low,b),link(u,tt,low,0);
            if (up-low)
                link(u,v,up-low,b);
        }
    }
    zkw();
    printf("%d\n",mincost);
    return 0;
}

总结

题目的难点主要在拆边,事实上,这似乎有点套路啊……
所以当直接做不方便时,可以试着作差,然后就出来一些东西……

原文地址:https://www.cnblogs.com/jz-597/p/11147880.html

时间: 2024-07-31 04:26:23

[JZOJ3302] 【集训队互测2013】供电网络的相关文章

UOJ#191. 【集训队互测2016】Unknown

题意:维护一个数列,每个元素是个二维向量,每次可以在后面加一个元素或者删除一个元素.给定P(x,y),询问对于[l,r]区间内的元素$S_i$,$S_i \times P$的最大值是多少. 首先简单地推出类似斜率优化的式子,那么我们需要在凸包上二分. 学习了一下这份代码http://uoj.ac/submission/69959 使用线段树按下标维护凸包.那么这里有一个问题,如果按照传统的写法,合并一次的复杂度是与$O(区间长度)$的,这样会导致单次插入/删除的时间复杂度变为$O(n)$,是不能

集训队互测2016Unknown(UOJ191)

题目链接 前面部分和lzz的题解是一样的. 首先将输入点(x,y)变为(-y,x)然后,只需找一个向量与(-y,x)的点积最大,即找一个向量在(-y,x)上的投影最长.此时所有的点都是在x轴上方的,容易发现答案一定是在凸包上的,再继续观察,如果有一个点在凸包而不在上凸包上,那么它的右上角及左上角一定有一个点,因此这个点一定不是最优的,所以答案一定在上凸包上,且可以在上凸包上二分. 对于subtask5,使用线段树,每个节点存储这个区间的凸包,合并凸包的话可以将两个凸包上的点归并后线性做凸包. 从

EZ 2018 05 06 NOIP2018 慈溪中学集训队互测(五)

享受爆零的快感 老叶本来是让初三的打的,然后我SB的去凑热闹了 TM的T2写炸了(去你妹的优化),T1连-1的分都忘记判了,T3理所当然的不会 光荣革命啊! T1 思维图论题,CHJ dalao给出了正解但-1输成0了缅怀 而且这题不能用读优玄学 思路也很新奇,先跑一遍MST,判断是否有无解的情况 然后看一下MST中与1相连的边有几条 如果小于k那么我们把所有与1相连的边减上一个值使它们优先被选,然后跑MST 大于k就加上去即可 注意到这个值可以二分,因此不停做MST即可 CODE #inclu

jzoj 2867. 【集训队互测 2012】Contra

Description 偶然间,chnlich 发现了他小时候玩过的一个游戏"魂斗罗",于是决定怀旧.但是这是一个奇怪的魂斗罗 MOD. 有 N 个关卡,初始有 Q 条命. 每通过一个关卡,会得到 u 分和1条命,生命上限为 Q.其中 u=min(最近一次连续通过的关数,R). 若没有通过这个关卡,将会失去1条命,并进入下一个关卡. 当没有生命或没有未挑战过的关卡时,游戏结束,得到的分数为每关得到的分数的总和. 由于 chnlich 好久不玩这个游戏了,每条命通过每个关卡的概率均为p(

[JZOJ2866] 【集训队互测 2012】Bomb

题目 题目大意 给你一个有\(n\)个点的平面. 选择三个点,求两两之间曼哈顿距离和的最大值和最小值. 思考历程&正解 比赛的时候没有想太多,但感觉似乎比较水-- 首先有个很显然的性质,答案为这三个点的最大最小横坐标之差和最大最小纵坐标之差的和. 可以把它看成矩形的周长,容易发现矩形至少一个顶点是三个点之一. 后来才发现水的是求最大值,而不是求最小值. 比赛之后开始和WMY刚-- 最大值是很好求的.我一开始打了个线段树来求,后来发现根本不用-- 求出所有点的\(Xmin,Xmax,Ymin,Ym

jzoj 2866. 【集训队互测 2012】Bomb

Description 给你\(n\)个点,坐标分别为\((xi,yi)\).从中取出三个点,使得其两两间曼哈顿距离和最大和最小,求最大值和最小值. 对于 100% 的数据, N<=100000 , 0<=Xi,Yi<=10^8 Solution 看完题目后感觉要分类讨论,思考1h后果断暴力O(n^3). 但是判断了一下n<=500才跑暴力,得了30分.(?10^9过4s?不判50分) 其实可以O(n^2)暴力的.(听说O(n^2)+优化 = 100) 对于第一个最大值,我们可以\

【uoj#94】【集训队互测2015】胡策的统计(集合幂级数)

题目传送门:http://uoj.ac/problem/94 这是一道集合幂级数的入门题目.我们先考虑求出每个点集的连通生成子图个数,记为$g_S$,再记$h_S$为点集$S$的生成子图个数,容易发现,$h_S=2^{size_S}$,其中$size_S$为点集$S$的极大生成子图内的边数.特殊的,$f_{\o}=g_{\o}=0$. 定义集合幂级数的乘法为子集卷积,考虑集合幂级数$h$和$g$的关系,我们可以得到 $$h=1+\sum_{k \geq 1}\frac{g^k}{k!}=1+e^

[集训队互测2012]calc——DP

题面 Bzoj2655 解析 可以强制让$a$数列递增,最后乘以$n!$ 有一个显然的$dp$,$f[i][j]$表示填前$i$个位置,且填的数最大不超过$j$的序列权值和,易有:$f[i][j] = f[i][j-1] + f[i-1][j-1] * j$ $O(AN)$的$dp$显然会$T$ 设$f[i][j]$是关于$j$的$g(i)$次多项式,$g(0)=0$ 注意到$f[i][j] - f[i][j-1]$就是关于$j$的$g(i)-1$次多项式,将$dp$方程移项:$f[i][j]

HDU - 4734 F(x) (2013成都网络赛,数位DP)

题意:求0-B的满足<=F[A]的所有可能 思路:数位DP,记忆化搜索 #include <iostream> #include <cstring> #include <algorithm> #include <cstdio> using namespace std; int A, B; int dp[20][200000]; int bit[20]; int dfs(int cur, int num, int flag) { if (cur == -