最小割 ---- 集合冲突模型 ----- P1646 [国家集训队]happiness

题面:

高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。


题解:

  1. 我们考虑用最小割解决问题.先把所有收益相加,再减去损失的代价。
  2. 因为每个人只有两种选择就是很典型的最小割去解决二元关系集合冲突模型
  3. 考虑用网络流求解,总量减去最小割即为答案。
    对于每个点 (i,j),从 s 连一条容量为选择文科的边,到 tt 连一条容量位选择理科的边。
    对于 (i,j) 和 (i+1,j) 两个点的组合情况。假设这两个点同时选文科有 w 的喜悦值,我们新建一个节点 x,从 s 向 x 连一条容量为喜悦值 w 的边,再从 x 向 (i,j) 和 (i+1,j) 分别连一条容量为 INF 的边。对于左右前后、文科理科同理!
    考虑这样做法的正确性:每个点自然只能选择一个科目(文科或理科),当某个点选择了文科 s,那么它向理科 t 的边都应该要被断开。考虑哪些边会被断开:首先是它直接连向 t 的边,其次是它和别的点组合连向 t 的边,这样一来,这些边在网络图的割中是有贡献的,意味着这些边的容量在答案中没有贡献,正确性证明完毕。

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = N;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args)  {
   read(first);
   read(args...);
}
struct node {
   int to, next, len; 
}e[maxn];
int head[maxn], cnt;
int n, m, s, t;
inline void add(int from, int to, int len) {
   e[cnt] = {to,head[from],len};
   head[from] = cnt ++;
}

int d[maxn],cur[maxn];
int pre[maxn], flow[maxn];

bool bfs() {
   ms(d,0);
   queue<int> q;
   q.push(s); d[s] = 1;
   while(!q.empty()) {
      int u = q.front(); q.pop();
      for(int i = head[u]; ~i; i = e[i].next) {
         int v = e[i].to;
         if(d[v] || e[i].len <= 0) continue;
         q.push(v);
         d[v] = d[u] + 1;
      }

   }      
   for(int i = 0; i <= t; ++ i) cur[i] = head[i];
   return d[t] != 0;
}

int dfs(int u, int flow) {
    if(u == t) return flow;
    for(int &i = cur[u]; ~i; i = e[i].next) {
        int v = e[i].to;
        if(d[u] + 1 != d[v] || e[i].len <= 0) continue;
        int delta = dfs(v,min(flow,e[i].len));
        if(delta <= 0) continue;
        e[i].len -= delta;
        e[i^1].len += delta;
        return delta;
    }
    return 0;
}

int get_maxflow() {
    int maxFlow = 0, delta;
    while(bfs())//bfs进行构建最短路网络
         while(delta = dfs(s,INF))
             maxFlow += delta;
    return maxFlow;
}
int Tn, Tm;
int sum;
inline int getid(int i, int j) {
    return (i - 1) * Tm + j;
}
int main() {
    IOS;
    ms(head,-1);
    cin >> Tn >> Tm;
    s = 0, t = Tn*Tm+(Tn-1)*Tm*2+Tn*(Tm-1)*2 + 1;
    for(int i = 1; i <= Tn; ++ i)
       for(int j = 1; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           add(s,id,x);
           add(id,s,0);
       }
    
    for(int i = 1; i <= Tn; ++ i)
       for(int j = 1; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           add(id,t,x);
           add(t,id,0);
       }

    int idx = Tn * Tm + 1;
    for(int i = 2; i <= Tn; ++ i) 
       for(int j = 1; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           int id2 = getid(i-1,j);
           add(s,idx,x);
           add(idx,s,0);

           add(idx,id,INF);
           add(id,idx,0);

           add(idx,id2,INF);
           add(id2,idx,0);

           idx++;
       }

    for(int i = 2; i <= Tn; ++ i) 
       for(int j = 1; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           int id2 = getid(i-1,j);
           add(t,idx,0);
           add(idx,t,x);

           add(idx,id,0);
           add(id,idx,INF);

           add(idx,id2,0);
           add(id2,idx,INF);

           idx++;
       }
    
    for(int i = 1; i <= Tn; ++ i) 
       for(int j = 2; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           int id2 = getid(i,j-1);
           add(s,idx,x);
           add(idx,s,0);

           add(idx,id,INF);
           add(id,idx,0);

           add(idx,id2,INF);
           add(id2,idx,0);

           idx++;
       }

    for(int i = 1; i <= Tn; ++ i) 
       for(int j = 2; j <= Tm; ++ j) {
           int x;
           cin >> x;
           sum += x;
           int id = getid(i,j);
           int id2 = getid(i,j-1);
           add(t,idx,0);
           add(idx,t,x);

           add(idx,id,0);
           add(id,idx,INF);

           add(idx,id2,0);
           add(id2,idx,INF);

           idx++;
       }
    cout << sum - get_maxflow();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值