2024江苏省赛 H. 完蛋,我被房产包围了 【费用流、分时图】

完蛋,我被房产包围了

n ≤ 200 , ∑ n ≤ 1 0 4 n \leq 200, \sum n \leq 10^4 n≤200,∑n≤104

求出最大利润

思路

每个代理商每次买房狂潮只能卖出 1 1 1 套房子,小红卖出一套房子贬值 1 1 1 元,小绿卖出一套房子贬值 ⌈ a i 10 ⌉ \lceil \dfrac{a_i}{10} \rceil ⌈10ai⌉,小蓝卖出一套房子不贬值。

因此我们应该尽可能让小蓝 卖房子,其次再交给小红 ,最后没得选择才交给小绿,因为卖出房子总归是有利润的,只是多少的问题,所以最优的策略一定是尽可能卖更多数量的房子。

我们先将小红和小蓝占满,让他们每次狂潮都有房子卖,他们卖不完的房子再交给贬值最多的小绿 ,由于小绿优先选择价值最高的房子贩卖,所以我们把不需要卖的房子交给小绿,并不会影响我们最终的最大利润决策

通过上述的分析,我们不难发现:对于每一次 买房狂潮 ,我们应该尽可能跑满最大流 ,并且要最小化贬值 ,也就是最小费用最大流

我们可以这样建模:

  1. 对于每个代理商 j j j,建立其在时刻 i i i 的点: i d ( i , j ) id(i, j) id(i,j),这里总共有 3 n 3n 3n 个分时点
  2. 对于每个代理商 j j j 的每个时刻点,我们连边 i d ( i − 1 , j ) → i d ( i , j ) id(i - 1, j) \rarr id(i, j) id(i−1,j)→id(i,j),容量为 ∞ \infty ∞,费用为 0 0 0,表示这个代理商手里持有的房子,随着时间推移而保留
  3. 对于每次买房狂潮,我们对当前时刻 i i i 的每个代理商 j j j 连边: i d ( i , j ) → T id(i, j) \rarr T id(i,j)→T,容量为 1 1 1,费用为 0 0 0,表示每个代理商在这个时刻最多贩卖一套房子
  4. 对于每个房子 x x x,连边: S → x S \rarr x S→x,容量为 1 1 1,费用为 0 0 0,表示这个房子最多被卖 1 1 1 次(这里新增 O ( n ) O(n) O(n) 个点),连边: x → T x \rarr T x→T,容量为 1 1 1,费用为 a x a_x ax,表示这个房子不交给代理商,最后没有被卖出去的贬值,也即是一块钱利润都没有
  5. 对于每个房子 x x x,我们在其分配的时刻 i i i,对每个代理商连边:
    x → i d ( i , 0 ) x \rarr id(i, 0) x→id(i,0),容量为 1 1 1,费用为 1 1 1,表示通过小红 卖这套房要贬值 1 1 1;
    x → i d ( i , 1 ) x \rarr id(i, 1) x→id(i,1) ,容量为 1 1 1,费用为 ⌈ a i 10 ⌉ \lceil \frac{a_i}{10} \rceil ⌈10ai⌉,表示通过小绿 卖这套房要贬值 ⌈ a i 10 ⌉ \lceil \frac{a_i}{10} \rceil ⌈10ai⌉;
    x → i d ( i , 2 ) x \rarr id(i, 2) x→id(i,2) ,容量为 1 1 1,费用为 0 0 0,表示通过小蓝卖不贬值

最后我们用 ∑ a i − ( S → T \sum a_i - (S \rarr T ∑ai−(S→T 的最小费用最大流) 就是答案了

cpp 复制代码
#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

struct MCF {
    struct Edge {
        int v, c, w; //边终点、容量、费用
        Edge(int v, int c, int w) : v(v), c(c), w(w) {}
    };
    const int n;
    std::vector<Edge> e;
    std::vector<std::vector<int>> g;
    std::vector<ll> h, dis;
    std::vector<int> pre;
    bool dijkstra(int s, int t) {
        dis.assign(n + 1, std::numeric_limits<ll>::max());
        pre.assign(n + 1, -1);
        std::priority_queue<std::pair<ll, int>, std::vector<std::pair<ll, int>>, std::greater<std::pair<ll, int>>> que;
        dis[s] = 0;
        que.emplace(0, s);
        while (!que.empty()) {
            ll d = que.top().first;
            int u = que.top().second;
            que.pop();
            if (dis[u] < d) continue;
            for (int i : g[u]) {
                int v = e[i].v;
                int c = e[i].c;
                int w = e[i].w;
                if (c > 0 && dis[v] > d + h[u] - h[v] + w) {
                    dis[v] = d + h[u] - h[v] + w;
                    pre[v] = i;
                    que.emplace(dis[v], v);
                }
            }
        }
        return dis[t] != std::numeric_limits<ll>::max();
    }
    MCF(int n) : n(n), g(n + 1) {}
    void addEdge(int u, int v, int c, int w) {
        g[u].push_back(e.size());
        e.emplace_back(v, c, w);
        g[v].push_back(e.size());
        e.emplace_back(u, 0, -w);
    }
    std::pair<int, ll> flow(int s, int t) {
        int flow = 0;
        ll cost = 0;
        h.assign(n + 1, 0);
        while (dijkstra(s, t)) {
            for (int i = 1; i <= n; ++i) h[i] += dis[i];
            int aug = std::numeric_limits<int>::max();
            for (int i = t; i != s; i = e[pre[i] ^ 1].v) aug = std::min(aug, e[pre[i]].c);
            for (int i = t; i != s; i = e[pre[i] ^ 1].v) {
                e[pre[i]].c -= aug;
                e[pre[i] ^ 1].c += aug;
            }
            flow += aug;
            cost += ll(aug) * h[t];
        }
        return std::make_pair(flow, cost);
    }
};

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        int n;
        std::cin >> n;
        MCF mcf(4 * n + 10);
        int tot = 3 * n + 1;
        int S = ++tot, T = ++tot;

        auto get_id = [&](int time, int j) -> int {
            return n * j + time;
        };

        int ans = 0;
        fore(i, 0, n){
            if(i > 0){
                fore(j, 0, 3){
                    int lst = get_id(i - 1, j), now = get_id(i, j);
                    mcf.addEdge(lst, now, INF, 0);
                }
            }

            int opt;
            std::cin >> opt;
            if(opt == 1){
                int w;
                std::cin >> w;
                ans += w;
                int id = ++tot;
                mcf.addEdge(S, id, 1, 0);
                mcf.addEdge(id, get_id(i, 0), 1, 1);
                mcf.addEdge(id, get_id(i, 1), 1, (w + 9) / 10);
                mcf.addEdge(id, get_id(i, 2), 1, 0);
                mcf.addEdge(id, T, 1, w);
            }
            else{
                fore(j, 0, 3){
                    int now = get_id(i, j);
                    mcf.addEdge(now, T, 1, 0);
                }
            }
        }

        ans -= mcf.flow(S, T).se;

        std::cout << ans << endl;
    }
    return 0;
}
相关推荐
XH华4 小时前
初识C语言之二维数组(下)
c语言·算法
南宫生5 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
不想当程序猿_5 小时前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
落魄君子5 小时前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
菜鸡中的奋斗鸡→挣扎鸡5 小时前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin5 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码5 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
scan7246 小时前
LILAC采样算法
人工智能·算法·机器学习
菌菌的快乐生活6 小时前
理解支持向量机
算法·机器学习·支持向量机
大山同学6 小时前
第三章线性判别函数(二)
线性代数·算法·机器学习