zoukankan      html  css  js  c++  java
  • 寿司餐厅[SHOI2017]

    题目描述

    Kiana 最近喜欢到一家非常美味的寿司餐厅用餐。

    每天晚上,这家餐厅都会按顺序提供 (n) 种寿司,第 (i) 种寿司有一个代号 (a_i) 和美味度 (d_{i, i}),不同种类的寿司有可能使用相同的代号。每种寿司的份数都是无限的,Kiana 也可以无限次取寿司来吃,但每种寿司每次只能取一份,且每次取走的寿司必须是按餐厅提供寿司的顺序连续的一段,即 Kiana 可以一次取走第 (1, 2) 种寿司各一份,也可以一次取走第 (2, 3) 种寿司各一份,但不可以一次取走第 (1, 3) 种寿司。

    由于餐厅提供的寿司种类繁多,而不同种类的寿司之间相互会有影响:三文鱼寿司和鱿鱼寿司一起吃或许会很棒,但和水果寿司一起吃就可能会肚子痛。因此,Kiana 定义了一个综合美味度 (d_{i, j} (i < j)),表示在一次取的寿司中,如果包含了餐厅提供的从第 (i) 份到第 (j) 份的所有寿司,吃掉这次取的所有寿司后将获得的额外美味度。由于取寿司需要花费一些时间,所以我们认为分两次取来的寿司之间相互不会影响。注意在吃一次取的寿司时,不止一个综合美味度会被累加,比如若 Kiana 一次取走了第 (1, 2, 3) 种寿司各一份,除了 (d_{1, 3}) 以外,(d_{1, 2}, d_{2, 3})也会被累加进总美味度中。

    神奇的是,Kiana 的美食评判标准是有记忆性的,无论是单种寿司的美味度,还是多种寿司组合起来的综合美味度,在计入 Kiana 的总美味度时都只会被累加一次。比如,若 Kiana 某一次取走了第 (1, 2) 种寿司各一份,另一次取走了第 (2, 3) 种寿司各一份,那么这两次取寿司的总美味度为 (d_{1, 1} + d_{2, 2} + d_{3, 3} + d_{1, 2} + d_{2, 3}),其中 (d_{2, 2}) 只会计算一次。

    奇怪的是,这家寿司餐厅的收费标准很不同寻常。具体来说,如果 Kiana 一共吃过了 (c (c > 0)) 种代号为 (x) 的寿司,则她需要为这些寿司付出 (mx^2 + cx),其中 (m) 是餐厅给出的一个常数。

    现在 Kiana 想知道,在这家餐厅吃寿司,自己能获得的总美味度(包括所有吃掉的单种寿司的美味度和所有被累加的综合美味度)减去花费的总钱数的最大值是多少。由于她不会算,所以希望由你告诉她。

    题面又臭又长

    输入格式

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

    输出格式

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

    题解

    注意一下 吃寿司的代价和你吃了多少种有关,而不是吃了多少个。。。

    第一眼看是DP 然而很快就发现有时候取重叠的区间可能更优 于是DP就没有了

    而且数据范围这么小 怎么看都是网络流 但是蒟蒻不会建图

    回头看了一下题解 发现其实还挺套路的。。。

    一个经典的最小割模型(好像还叫什么最大权闭合子图?)

    我们对于每个寿司区间都建立一个点 如果最小割割完后这个点在源点的联通块就表示我们选了这个区间 如果在汇点的联通块就表示没选

    1. 对于有着正美味度的区间([i,j]),从源点向它连一条流量为美味度(d_{i,j})的边,表示不选这个点要付出(d_{i,j})的代价
    2. 对于有着负美味度的区间([i,j]),从它向汇点连一条流量为(-d_{i,j})的边,表示选了这个点要付出(-d_{i,j})的代价

    因为如果选了([i,j]),必然选了它的所有子区间,所以我们对于每个区间([i,j])

    1. ([i,j])([i+1,j])([i,j+1])分别连一条流量为(inf)的边,表示选了([i,j])必须选剩下两个

    然后看看每个寿司的选择代价要怎么处理

    我们同样对于每个单个的寿司建一个点

    1. 对于每个寿司点(i),向汇点连一条流量为代号(a_i)的边 表示选过这个寿司要付出(a_i)的代价
    2. 从区间点([i,i])向寿司点(i)连一条流量为(inf)的边 表示选了前者必须选后者

    最后看看那个(mx^2)的代价怎么处理:

    如法炮制,我们对于每个寿司代号建一个点

    1. 对于代号点(x),向汇点连一条流量为(mx^2)的边
    2. 从每个代号为(x)的寿司点 向这个代号点(x)连一条(inf) 表示选了那个寿司就要付出代号的(mx^2)代价

    最小割等于最大流 用dinic求出

    然后我们注意到类型1的边是"不选它会付出(d_{i,j})"的代价 所以初始时我们把答案设置成所有正的美味度之和

    然后最终答案就是 所有正的美味度之和 减去最小割

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    template<typename T> 
    inline void read(T &num) {
    	T x = 0, _f = 1; char ch = getchar();
    	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') _f = -1;
    	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
    	num = x * _f; 
    }
    
    const int inf = 0x7fffffff;
    int n, m, s, t, c[205], val[205][205], d[12005], ans;
    int now[12005], head[12005], pre[500005], to[500005], flow[500005], sz = 1;
    int ind[205][205], indtp[1005], tot;
    bool vis[1005];
    
    inline void addedge(int u, int v, int w) {
    	pre[++sz] = head[u]; head[u] = sz; to[sz] = v; flow[sz] = w;
    } 
    
    bool bfs() {
        memset(d, 0, sizeof(d));
        queue<int> q;
        q.push(s); d[s] = 1;
        while (!q.empty()) {
            int x = q.front(); q.pop();
            for (int i = head[x]; i; i = pre[i]) {
                int y = to[i];
                if (flow[i] > 0 && d[y] == 0) {
                    d[y] = d[x] + 1;
                    q.push(y);
                }
            }
        }
        return (d[t] != 0);
    }
    
    int dfs(int x, int cur) {
        if (x == t || cur <= 0) return cur;
        int rest = cur;
        for (int i = now[x]; i; i = pre[i]) {
            now[x] = i; int y = to[i];
            if (flow[i] > 0 && d[y] == d[x] + 1) {
                int tmp = dfs(y, min(rest, flow[i]));
                if (tmp <= 0) d[y] = 0;
                flow[i] -= tmp; flow[i^1] += tmp;
                rest -= tmp;
                if (rest <= 0) break;
            }
        }
        return cur - rest;
    }
    
    int dinic() {
        int ret = 0;
        while (bfs()) {
            memcpy(now, head, sizeof(now));
            ret += dfs(s, inf);
        }
        return ret;
    }
    
    void preind() {
    	s = 0;
    	for (int i = 1; i <= n; i++) {
    		if (!vis[c[i]]) {
    			indtp[c[i]] = ++tot;
    			vis[c[i]] = 1;
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = i; j <= n; j++) {
    			ind[i][j] = ++tot;
    		}
    	}
    	t = ++tot;
    }
    
    int main() {
    	read(n); read(m);
    	tot = n;
    	for (int i = 1; i <= n; i++) {
    		read(c[i]);
    	}
    	for (int i = 1; i <= n; i++) {
    		for (int j = i; j <= n; j++) {
    			read(val[i][j]);
    		}
    	}
    	preind();
    	for (int i = 1; i <= n; i++) {
    		for (int j = i; j <= n; j++) {
    			if (val[i][j] >= 0) {
    				addedge(s, ind[i][j], val[i][j]); //边类型1
    				addedge(ind[i][j], s, 0);
    				ans += val[i][j];
    			} else {
    				addedge(ind[i][j], t, -val[i][j]); //边类型2
    				addedge(t, ind[i][j], 0);
    			}
    			if (i != j) {
    				addedge(ind[i][j], ind[i][j-1], inf); //边类型3
    				addedge(ind[i][j-1], ind[i][j], 0);
    				addedge(ind[i][j], ind[i+1][j], inf);
    				addedge(ind[i+1][j], ind[i][j], 0); 
    			}
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		addedge(ind[i][i], i, inf); addedge(i, ind[i][i], 0); //边类型5
    		addedge(i, t, c[i]); addedge(t, i, 0); //边类型4
    		addedge(i, indtp[c[i]], inf); addedge(indtp[c[i]], i, 0); //边类型7
    	}
    	for (int i = 1; i <= 1000; i++) {
    		if (vis[i] && m) {
    			addedge(indtp[i], t, m * i * i); addedge(t, indtp[i], 0); //边类型6
    		}
    	}
            printf("%d
    ", ans - dinic());
    	return 0;
    }
    
  • 相关阅读:
    2018.8.20 Python之路---常用模块
    2018.8.16 正则表达式
    2018.8.15 python中的冒泡法排序
    2018.8.15 python 中的sorted()、filter()、map()函数
    2018.8.14 python中的内置函数(68个)
    2018.8.13 python中生成器和生成器表达式
    2018.8.10 python中的迭代器
    2018.8.9 python中的动态传参与命名空间
    python测试开发django(1)--开始Hello World!
    UPC-5120 Open-Pit Mining(最大权闭合子图)
  • 原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM80.html
Copyright © 2011-2022 走看看