[WF2011]Chips Challenge

27 篇文章 0 订阅

题目

题意概要
有一个 n × n n\times n n×n 的棋盘,你需要在其中一些格子放置棋子,使得如下限制被满足:

  • 给定的若干格子上没有棋子。
  • 给定的若干格子上必须有棋子。
  • i    ( 1 ⩽ i ⩽ n ) i\;(1\leqslant i\leqslant n) i(1in) 行上棋子的个数与第 i i i 列上棋子的个数相等。
  • i    ( 1 ⩽ i ⩽ n ) i\;(1\leqslant i\leqslant n) i(1in) 行上棋子的个数不超过整个棋盘上棋子个数的 a b \frac{a}{b} ba

求最多能放多少个棋子。

数据范围与提示
n ⩽ 40 n\leqslant 40 n40 0 ⩽ a ⩽ b ⩽ 1 0 3 0\leqslant a\leqslant b\leqslant 10^3 0ab103

思路

首先,你需要以高瞻远瞩的眼界、坚不可摧的自信、过人的胆魄指出:这是网络流的题目!——反正我是做不到。并没有什么线索能够指向网络流。如果有,那就是:其他的都不行。

确定了是网络流,具体怎么流呢?尝试逐个翻译上面的条件后,想必大家都会和我一样发现,后两个限制是比较困难的。而最后一个限制有一个特点:如果我们考虑枚举每行的棋子个数的最大值 λ \lambda λ,那么限制条件是总量 ⩾ b a λ \geqslant\frac{b}{a}\lambda abλ,而目标是最大化总量;这就是说,目标和限制是平行的,在满足目标的同时,我们就让限制也尽可能得到了满足。这是一个突破口。既然有突破口,那还是有迹可循的。

最困难的限制是第 i i i 行棋子个数与第 i i i 列棋子个数相等。首先,它不是一个常量判断式,故不能直接用容量进行限制;而两个值都与流值有关,流量怎么作差呢?

关键点就在这里:流量不能作差,但是可以相加为定值。只要让两个点同时流到某个中转点,然后流到 T T T,限制这条边的流量并且要求满流,那么两个流量相加必须是容量!从这个角度出发,我们要试试把其中一个数转化为相反数。

i i i 列的棋子数量,等于什么减去什么呢?显然就是 n n n 减去第 i i i 列上没放棋子的格子数量。那么尝试建一下图,显然有点代表一行、有点代表一列,则二者同时连向一个辅助点,容量为 + ∞ +\infty +,辅助点连向 T T T,容量为 n n n 。只要让代表行的点上的流量是该行上放了棋子的格子数量,而代表列的点的流量是没放棋子的格子数量,就行了。

由于容量被用于作为限制条件,显然答案不是最小割,只能是 最小费用 了。回到我们网络的基本架构上:如果一个位置上放棋子,那么获得 1 1 1 的权值,并且给 i i i 行产生 1 1 1 的流量,给第 j j j 列减小 1 1 1 的流量。这样费用是负数,不太方便。不如反转一下状态:如果一个位置上不放棋子,那么产生 1 1 1 的费用,并且给第 i i i 行减小 1 1 1 的流量,给第 j j j 列增加 1 1 1 的流量。(当然,理论上二者没啥区别。)初始让整个棋盘所有可以放棋子的位置都放上一枚棋子。

流量的一增一减,其实就是直接连边呗!所以我们把建图方式说得更清晰一点:

  • 源点 S S S 向代表一行的点 r i r_i ri 连边,容量为第 i i i 行上可以放棋子的格子数量,费用为 0 0 0
  • 如果 ( i , j ) (i,j) (i,j) 是可以不放棋子(而非必须不放)的格子,则 r i r_i ri 向代表一列的点 c j c_j cj 连边,容量为 1 1 1,费用为 1 1 1
  • r i r_i ri c i c_i ci 都向辅助点 p i p_i pi 连边,容量为 + ∞ +\infty +,费用为 0 0 0
  • 辅助点 p i p_i pi 向汇点 T T T 连边,容量为第 i i i 列上可以放棋子的格子数量,费用为 0 0 0

好,这道题就解决了!——阿拉,险些把 a b \frac{a}{b} ba 的限制给忘了!我们要枚举每一行棋子数量的最大值 f f f 才行!枚举之后,我们就可以直接求出最优值,然后判断 a b \frac{a}{b} ba 是否成立来更新答案。原因在上面已说过了。显然不会漏解。

现在来想想,什么是第 i i i 行只能有至多 f f f 个棋子呢?只要看看图中哪条边能代表第 i i i 行的实际棋子个数,然后以 f f f 作为容量限制即可。第 i i i 行本来有多少个棋子,就有多少流量停留在 r i r_i ri 上,而每通过费用为 1 1 1 的边流出 1 1 1 的流量,就会拿走一个棋子。剩下的流量就是实际棋子个数!剩下的流量在哪里?全都流向了辅助点 p i p_i pi,无一例外!所以

  • r i r_i ri 向辅助点 p i p_i pi 连边,容量由 + ∞ +\infty + 改为 f f f,费用仍然为 0 0 0

然后大功告成。跑最小费用最大流即可。当然,其实该图中有一个冗余结构 c i → p i c_i\rightarrow p_i cipi,因为 c i c_i ci 无其他出边,此边容量为 + ∞ +\infty + 费用为 0 0 0,中间这条边并没有什么用。所以把 p i p_i pi c i c_i ci 合并起来就行。

最后,还有一个细节——也是不可忽视的要素——怎么跑最小费用流?注意到边权最大为 1 1 1 最小为 0 0 0,加上势函数之后可以让其变为 [ 0 , 2 ] [0,2] [0,2] 之内,于是用 k-bfs \text{k-bfs} k-bfs 即可做到 O ( 3 n + m ) \mathcal O(3n+m) O(3n+m) 。或者就用 s p f a \rm spfa spfa,也是接近线性的吧……?

反正我用迪彻斯特就调麻了,结果真的不是死循环的锅

代码

#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cassert>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
template < typename T >
inline void getMin(T &x,const T &y){
	if(y < x) x = y;
}

const int MAXR = 43;
const int MAXN = MAXR<<1;
const int MAXM = MAXR*MAXR+3*MAXR;
struct Edge{
	int to, nxt, capa, val;
	Edge() = default;
	Edge(int _to,int _nxt,int _capa,int _val)
	:to(_to),nxt(_nxt),capa(_capa),val(_val){ }
};
Edge e[MAXM<<1];
int head[MAXN], cntEdge;
void addEdge(int a,int b,int c,int d){
	e[cntEdge] = Edge(b,head[a],c,d);
	head[a] = cntEdge ++;
	e[cntEdge] = Edge(a,head[b],0,-d);
	head[b] = cntEdge ++;
}

const int INFTY = 0x7fffffff;
const int QUEUE_LENGTH = MAXN*MAXN;
int que[3][QUEUE_LENGTH], dis[MAXN];
bool inque[MAXN]; int pre[MAXN], h[MAXN];
void spfa(int x,const int &n){
	fill(dis+1,dis+n+1,INFTY);
	int fro[3] = {0,0,0}, bac[3] = {1,0,0};
	dis[que[0][0] = x] = 0; // init
	for(int i=0,lst=0; true; i=i+1-3*(i==2)){
		if(fro[i] == bac[i]){
			if(lst == i) break; // empty
			continue; /// do not modify @a lst
		}
		lst = i; // last updating time
		while(fro[i] != bac[i]){
			x = que[i][fro[i] ++];
			if(dis[x]%3 != i) continue;
			for(int j=head[x]; ~j; j=e[j].nxt)
				if(e[j].capa != 0){
					int w = h[x]+e[j].val-h[e[j].to];
					if(dis[e[j].to] > dis[x]+w){
						dis[e[j].to] = dis[x]+w;
						pre[e[j].to] = j; // record path
						const int p = (i+w)%3;
						que[p][bac[p] ++] = e[j].to;
					}
				}
		}
	}
	rep(i,1,n) if(dis[i] != INFTY)
		h[i] += dis[i]; // avoid overflow
}
void dickni(int source,int sink,const int &n,int &flow,int &cost){
	flow = cost = 0; memset(h+1,0,n<<2);
	while(spfa(source,n), dis[sink] != INFTY){
		int t = INFTY;
		for(int i=sink; i!=source; i=e[pre[i]^1].to)
			getMin(t,e[pre[i]].capa);
		flow += t, cost += t*h[sink];
		for(int i=sink; i!=source; i=e[pre[i]^1].to)
			e[pre[i]].capa -= t, e[pre[i]^1].capa += t;
	}
}

char maze[MAXR][MAXR];
int row[MAXR], col[MAXR];
int main(){
	for(int n,cas=0; scanf("%d",&n)==1; ){
		int son = readint(), mom = readint();
		if(!n && !son && !mom) break;
		memset(row+1,0,n<<2), memset(col+1,0,n<<2);
		int sum = 0, used = 0, ans = -1;
		rep(i,1,n){
			scanf("%s",maze[i]+1);
			rep(j,1,n) if(maze[i][j] != '/'){
				++ sum, ++ row[i], ++ col[j];
				used += (maze[i][j] == 'C');
			}
		}
		const int source = n<<1|1, sink = source+1;
		rep(bound,0,n){
			memset(head+1,-1,sink<<2);
			rep(i,(cntEdge=0)+1,n){
				addEdge(source,i,row[i],0);
				addEdge(i+n,sink,col[i],0);
				addEdge(i,i+n,bound,0); // at most
				rep(j,1,n) if(maze[i][j] == '.')
					addEdge(i,j+n,1,1); // remove it
			}
			int f, now; dickni(source,sink,sink,f,now);
			if(f == sum && bound*mom <= (sum-now)*son)
				ans = max(ans,sum-now);
		}
		printf("Case %d: ",++cas);
		if(!(~ans)) puts("impossible");
		else printf("%d\n",ans-used);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值