bzoj4873 [Shoi2017]寿司餐厅

Input

第一行包含两个正整数n,m,分别表示这家餐厅提供的寿司总数和计算寿司价格中使用的常数。
第二行包含n个正整数,其中第k个数ak表示第k份寿司的代号。
接下来n行,第i行包含n-i+1个整数,其中第j个数di,i+j-1表示吃掉寿司能
获得的相应的美味度,具体含义见问题描述。
N<=100,Ai<=1000

Output

输出共一行包含一个正整数,表示Kiana能获得的总美味度减去花费的总钱数的最大值。

Sample Input

3 1
2 3 2
5 -10 15
-10 15
15

Sample Output

12
分析:看出来这是一道网络流题,却不知道该如何建模......
   看到mx^2 + cx这个式子,我想起了bzoj1449的费用递增模型. 然而m和x都是固定的,每次的增量都是x,费用并不是递增的......
   然后我又想:选取若干个不同的寿司,它们之间会产生贡献. 似乎可以用最小割来做,类似bzoj3144的离散变量模型吗? 这道题选择的寿司之间没有任何限制,当然不是离散变量模型了. 
   单纯考虑最小割可以吗? 每次要求选一段连续的区间不好处理啊.
   上述尝试均失败后,果断打了暴力.
   如果把区间看作点,这道题差不多就做完了. 选择了区间[l,r],则必然会选择在[l,r]中的所有的点,这实际上就是最大权闭合子图模型. 每个区间要向所有区间内的点连边吗? 不用!利用传递关系:[l,r]向[l + 1,r]和[l,r - 1]连边.  当l == r时,就代表了一个点.
   费用要怎么处理呢? 将mx^2 + cx看作mx^2 和 cx两部分. 把编号也看作点. 如果一个编号被选择了,那么它的费用就是mx^2,不会改变.  如果一个点被选择了,那么它的费用就是它的编号.  所以每个编号向T连边,容量为m * i^2. 每个点向其所属的编号连边, 容量为inf.  每个点向T连边,容量为其编号.  这样的话选择了点i,就要割掉它到T的所有连边,即i的编号与T的连边和i与T的连边.
   具体的建图方式如下:

(参考sliverNebula的博客)

    可以这么理解:割掉S与点i的边,就是放弃了i.  割掉i与T的连边,就是选i的代价.

   做网络流的题要有把所有东西都看作“点”的想法.

 

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 20010,maxm = 120010,inf = 0x7fffffff;
int n,m,a[110],mx,d[110][110],cnt,pos[110][110],S,T,ans,head[maxn];
int to[maxm],nextt[maxm],w[maxm],tot = 2,vis[maxn],cur[maxn];

void add(int x,int y,int z)
{
    w[tot] = z;
    to[tot] = y;
    nextt[tot] = head[x];
    head[x] = tot++;

    w[tot] = 0;
    to[tot] = x;
    nextt[tot] = head[y];
    head[y] = tot++;
}

bool bfs()
{
    queue <int> q;
    q.push(S);
    memset(vis,-1,sizeof(vis));
    vis[S] = 0;
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        if (u == T)
            return true;
        for (int i = head[u];i;i = nextt[i])
        {
            int v = to[i];
            if (w[i] && vis[v] == -1)
            {
                vis[v] = vis[u] + 1;
                q.push(v);
            }
        }
    }
    return false;
}

int dfs(int u,int f)
{
    if (u == T)
        return f;
    int res = 0;
    for (int i = cur[u];i;i = nextt[i])
    {
        int v = to[i];
        if (w[i] && vis[v] == vis[u] + 1)
        {
            int temp = dfs(v,min(f - res,w[i]));
            w[i] -= temp;
            w[i ^ 1] += temp;
            res += temp;
            if (w[i])
                cur[u] = i;
            if (res == f)
                return res;
        }
    }
    if (!res)
        vis[u] = -1;
    return res;
}

void dinic()
{
    while (bfs())
    {
        for (int i = 1; i <= T; i++)
            cur[i] = head[i];
        ans -= dfs(S,inf);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d",&a[i]);
        mx = max(mx,a[i]);
    }
    cnt = mx + n;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++)
        {
            if (i == j)
                pos[i][j] = i;
            else
                pos[i][j] = ++cnt;
        }
    S = cnt + 1;
    T = S + 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n - i + 1; j++)
        {
            scanf("%d",&d[i][i + j - 1]);
            int l = i,r = i + j - 1;
            if (d[l][r] < 0)
                add(pos[l][r],T,-d[l][r]);
            else
            {
                add(S,pos[l][r],d[l][r]);
                ans += d[l][r];
            }
            if (l != r)
                add(pos[l][r],pos[l + 1][r],inf),add(pos[l][r],pos[l][r - 1],inf);
            else
            {
                add(pos[l][r],a[l] + n,inf);
                add(pos[l][r],T,a[l]);
            }
        }
    for (int i = 1; i <= mx; i++)
        add(i + n,T,i * i * m);
    dinic();
    printf("%d\n",ans);

    return 0;
}

 

posted @ 2018-03-26 14:20  zbtrs  阅读(192)  评论(0编辑  收藏  举报