BZOJ 4873: [Shoi2017]寿司餐厅 (最大权闭合子图)

来源:互联网 发布:上海美国学校 知乎 编辑:程序博客网 时间:2024/04/29 00:09

这里写图片描述

这里写图片描述

这里写图片描述


Solution

这题也如同day1第三题一样,是一道看懂题目并转化后发现是很水的题。。(这个前提非常重要)

考试时我想day1没考网络流,day2肯定有吧。没想到被我说中了。前两题耗了很久,第三题看题目那么长,想了想发现正解不是dp就是网络流(嘴巴AC),甚至想到跑最小割,但死活卡在构图,又去想dp,无果,然后就被迫弃了。。千古蒟蒻。。

讲讲正解。

考虑最大权闭合子图,对于长度大于1的区间,区间[i,j]要取的话,[i+1,j]和[i,j-1]都必须取(因为要取连续的一段),而题目中又是分次数取,但又有一些限制,记花费也有限制。这又是网络流的套路。
于是我们按最大权闭合子图构图。[i,j]向[i+1,j],[i,j-1]连边,容量为INF。
然后取费用就构出a[i](最多1000个),容量保存费用,连t。s向正权点连边,负权点向t连边。

对于长度为1的区间[i,i],无后继,将其价值减去a[i](将花费分解,注意题目中的c是没用的!),并且前置条件(连向的点)是权值为m*a[i]*a[i]的点。

然后跑最小割就行了。

这里复习一下最大权闭合子图的构图和作法。

最大权闭合图
定义一个有向图(有点权)的闭合图(closure)是该有向图的一个点集,且该点集的所有出边都还指向该点集。即闭合图内的任意点的任意后继也一定在闭合图中。
按照上面的定义,闭合图是允许超过一个连通块的
给每个点 分配一个点权 (任意实数,可正可负)。
最大权闭合图是指一个点权之和最大的闭合图

算法
1、在原图上增加源 s和汇t。
2、将原图每条边(u,v)替换为容量为+oo 的有向边 (u, v)。
3、由s到原图每个正权点 连有向边,容量为该点点权
4、由每个负权点向t连容量为-点权的有向边

这里写图片描述

这里写图片描述

最大权为。。。
感性认识
先全部正权都选了。
割s的出边即不选这个点。
割t的入边即选了这个点
若存在s到t路径则不满足这条路径所代表的依赖关系。
故答案为
正权和减最小割

复习到这里,按题目要求构完图后剩下的就是Dinic跑最小割了。。


Code

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#include <queue>#include <cstdlib>#define INF 0x7fffffff#define N 111#define An 1000#define maxn N * N + Anusing namespace std;int n, m, s, t, sum;int a[N], d[N][N], num[N][N];int level[maxn], iter[maxn], head_p[maxn], cur = -1;struct Tadj{int next, obj, cap;} Edg[N*N*6+An*2];queue <int> q;void Insert(int a, int b, int c){    cur ++;    Edg[cur].next = head_p[a];    Edg[cur].obj = b;    Edg[cur].cap = c;    head_p[a] = cur;}bool bfs(){    q.push(s);    memset(level, -1, sizeof(level));    level[s] = 0;    while(!q.empty()){      int now = q.front();      q.pop();      for(int i = head_p[now]; ~ i; i = Edg[i].next){        int v = Edg[i].obj, c = Edg[i].cap;        if(c && level[v] == -1){          level[v] = level[now] + 1;          q.push(v);        }      }    }    return ~level[t];}int Dinic(int now, int f){    if(now == t)  return f;    for(int &i = iter[now]; ~ i; i = Edg[i].next){      int v = Edg[i].obj, c = Edg[i].cap;      if(c && level[now] < level[v]){        int d = Dinic(v, min(f, c));        if(d){          Edg[i].cap -= d;          Edg[i^1].cap += d;          return d;        }      }    }    return 0;}int Min_cut(){    int flow = 0, f;    for(;;){      if(!bfs())  return flow;      for(int i = s; i <= t; i++)  iter[i] = head_p[i];      while((f = Dinic(s, INF)) > 0)  flow += f;    }}int main(){    freopen("sushi.in", "r", stdin);    freopen("sushi.out", "w", stdout);    scanf("%d%d", &n, &m);    for(int i = 1; i <= n; ++i)  scanf("%d", &a[i]);    s = 1;  t = s + An + n * n + 1;    for(int i = s; i <= t; i++)  head_p[i] = -1;    for(int i = 1; i <= An; i++){      Insert(s+i, t, m * i * i);      Insert(t, s+i, 0);    }    int cnt = 1;    for(int i = 1; i <= n; i++)     for(int j = i; j <= n; j++){       scanf("%d", &d[i][j]);       num[i][j] = s + An + cnt ++;     }    for(int i = 1; i <= n; i++)     for(int j = i; j <= n; j++){       if(i == j){         d[i][j] -= a[i];         Insert(num[i][j], s+a[i], INF);         Insert(s+a[i], num[i][j], 0);       }       else{         Insert(num[i][j], num[i+1][j], INF);         Insert(num[i+1][j], num[i][j], 0);         Insert(num[i][j], num[i][j-1], INF);         Insert(num[i][j-1], num[i][j], 0);       }       if(d[i][j] > 0){         sum += d[i][j];         Insert(s, num[i][j], d[i][j]);         Insert(num[i][j], s, 0);       }       else{         Insert(num[i][j], t, -d[i][j]);         Insert(t, num[i][j], 0);       }     }    printf("%d\n", sum - Min_cut());    return 0;}

Victory won’t come to you unless you go to it.

2 0