【CLYZ集训】催眠大师【费用流】

119 篇文章 0 订阅

题目大意

给定一个 n × m n\times m n×m 的棋盘,棋盘上每个位置要么为空要么为障碍。定义棋盘上两个位置 ( x , y ) , ( u , v ) (x,y),(u,v) (x,y),(u,v)能互相攻击当前仅当满足以下两个条件:
1. x = u x=u x=u y = v y=v y=v
2.对于 ( x , y ) (x,y) (x,y) ( u , v ) (u,v) (u,v)之间的所有位置,均不是障碍。
现在有 q q q个询问,每个询问给定 m m m,要求从棋盘中选出 m m m个空位置来放棋子,问最少互相能攻击到的棋子对数是多少?

思路:

考虑费用流。对于每一行和每一列,连续没有障碍的格子组成一个连续段。如果当前位置为 ( i , j ) (i,j) (i,j)且不是障碍,则该横连续段向该列连续段连一条容量为1的边,费用为0。代表这里放不会有贡献。
然后源点向所有横连续段建边,边的容量为连续段的大小x,费用为 ( x 2 ) \binom{x}{2} (2x)
然后汇点同理。
但显然这样是不行的,因为一个连续段放的棋子个数是不确定的。所以分开建边,建x条边,第i条容量为1,费用为i-1,这样就可以满足 ( x 2 ) \binom{x}{2} (2x)的条件。
然后每次增加1流量,跑费用流。

c o d e code code

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

using namespace std;

const int MAXN = 100;

int n, m, m2, S, T;
int a[MAXN][MAXN], r[MAXN][MAXN], c[MAXN][MAXN], d[MAXN * MAXN];
int ans[MAXN * MAXN];
int head[MAXN * MAXN], crn[MAXN * MAXN], tot = 1, last[MAXN * MAXN];
int dis[MAXN * MAXN];
struct node {
	int to, next, w, dis;
}b[MAXN * MAXN * 2];
bool v[MAXN * MAXN];

void add(int x,int y,int w,int dis) {
	b[++ tot] = (node) {y, head[x], w, dis};
	head[x] = tot;
}

bool spfa() {
	memset(v, 0, sizeof(v));
	memset(dis, 0x3f3f3f3f3f, sizeof(dis));
	dis[S] = 0;
	queue<int> q;
	q.push(S);
	while(!q.empty()) {
		int x = q.front();
		q.pop();
		for(int i = head[x]; i; i = b[i].next) {
			int y = b[i].to;
			if(b[i].w > 0 && dis[y] > dis[x] + b[i].dis) {
				dis[y] = dis[x] + b[i].dis;
				crn[y] = x;
				last[y] = i;
				if(!v[y]) v[y] = 1, q.push(y);
			}
		}
		v[x] = 0;
	}
//	cout<<dis[T]<<endl;
	return dis[T] != 1061109567;
}

void dinic() {
	int x = 1;
	while(spfa()) {
		ans[x] = ans[x - 1] + dis[T];
//		cout<<ans[x]<<endl;
		for(int i = T; i != S; i = crn[i]) {
			b[last[i]].w --;
			b[last[i] ^ 1].w ++;
		}
		x ++;
		if(x > n * n) return ;
	}
}

int main() {
	freopen("table.in", "r", stdin);
	freopen("table.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++) {
		string s;
		cin>>s;
		for(int j = 0; j < s.size(); j ++)
			if(s[j] == '.') a[i][j + 1] = 1;
			else a[i][j + 1] = -1;
	}
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j <= n; j ++) {
			if(a[i][j] == -1) continue;
			if(a[i][j - 1] != 1) r[i][j] = ++ m;
			else r[i][j] = r[i][j - 1];
		}
	}
	for(int j = 1; j <= n; j ++) {
		for(int i = 1; i <= n; i ++) {
			if(a[i][j] == -1) continue;
			if(a[i - 1][j] != 1) c[i][j] = ++ m2;
			else c[i][j] = c[i - 1][j];
		}
	}
	S = 0, T = m + m2 + 1; 
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++) {
			if(a[i][j] == -1) continue;
			add(r[i][j], c[i][j] + m, 1, 0);
			add(c[i][j] + m, r[i][j], 0, 0);
			d[r[i][j]] ++, d[c[i][j] + m] ++;
		}
	for(int i = 1; i <= m; i ++)
		for(int j = 1; j <= d[i]; j ++)
			add(S, i, 1, j - 1), add(i, S, 0, j - 1);
	for(int i = m + 1; i <= m + m2; i ++)
		for(int j = 1; j <= d[i]; j ++)
			add(i, T, 1, j - 1), add(T, i, 0, j - 1);
	dinic();
	int q;
	scanf("%d", &q);
	while(q --) {
		int x;
		scanf("%d", &x);
		printf("%d\n", ans[x]);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值