分析
之所以把这两题放在一起,是因为它们有很多的共同点。
单看题目背景就知道其实它们挺类似的:都是在棋盘(网格)中,且点与点之间有相互的排斥关系。实际上它们的解法思想也几乎一模一样。
方格取数
先讲方格取数。
首先我们发现这类问题的最重要的特点:点与点之间会互相排斥,且每个点只有选与不选两种状态。于是我们可以考虑构建二分图,并建立超级源点 s s s和汇点 t t t,最后求最小割(最大流)。具体建立过程如下:
- 首先把网格进行黑白染色。比如把横纵坐标和为奇数的染成黑色,和为偶数的染成白色,于是相邻的黑白点就不能同时被选。这一步并不需要真正用代码进行染色,只需要在建图的时候判断横纵坐标和即可,同时也是方便后面理解。
- 于是我们考虑把所有黑点放在二分图的左侧,如果一个黑点与 s s s有连边,就代表它被选。同理把白点放在右边,如果与 t t t连边,就代表白点被选。考虑边的权值,显然如果一个点(无论黑白)被选,价值就是当前点的点权,于是黑白点向 s s s和 t t t连的边边权都等于点权。
- 接着考虑限制条件,显然如果我们要让两个点中最多选一个,那么我们肯定要让其中一个点向 s s s或 t t t的连边在求最小割的时候被割掉(可以重新看看2中的定义)。于是我们对于每个黑点,向受其影响的白点连边权为 I N F INF INF的边,由于最小割的性质,边权为 I N F INF INF的边不可能被割掉。
- 跑最小割(最大流)模板,最后的答案为点权总和减去最小割。
代码(建图)
Show You the Code.
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
scanf("%d", &a[i][j]);
sum += a[i][j];
if((i+j)&1){ //判断黑白点
add(s, get(i, j), a[i][j]);
add(get(i, j), s, 0);
}
else{
add(get(i, j), t, a[i][j]);
add(t, get(i, j), 0);
}
}
}
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
for(int k = 0; k < 4; k++){ //四个方向都会影响到
if(!((i+j)&1)) continue;
int u = i+mx[k], v = j+my[k];
if(u<=0 || u>m || v<=0 || v>n) continue;
add(get(i, j), get(u, v), INF); //get(x,y)把二维坐标转化为一位编号. get(x,y)=(n-1)*x+y
add(get(u, v), get(i, j), 0);
}
}
}
dinic();
cout << sum-ans << endl; //ans是最大流
小小的小结
回过头来看看我们在这道题中建图后的本质操作是什么。
我们考虑最后被割成两半的图,图中互相影响的点一定不再同一个集合中,而我们建图的时候是有影响就建边,所以其实我们是从初始的二分图中找出了最大的独立集(也就是最大的点集,其中的点两两之间都没有连边)。
骑士共存
(讲完上一题应该都会骑士共存了)
同样的,我们发现虽然影响的方式变了,但是假如我们依旧用同样的方式染色,黑白点仍旧只会互相影响,而不会直接影响同色的点(可以看题目中的图)。这说明我们其实完全可以按照一样的套路来乱搞。
这道题多了障碍,实际上并没用,遇到障碍的时候直接跳过不连边就行了。
代码(还是建图)
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if(mp[i][j]) continue; //mp[i][j]为1则表示有障碍
if((i+j)&1){
add(s, get(i,j), 1); //点权实际可以看做1
add(get(i,j), s, 0);
}
else{
add(get(i,j), t, 1);
add(t, get(i,j), 0);
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if(mp[i][j] || !((i+j)&1)) continue;
int u, v;
for (int k = 0; k < 8; ++k) {
u = i+mx[k], v = j+my[k];
if(u<=0 || u>n || v<=0 || v>n || mp[u][v]) continue;
add(get(i, j), get(u, v), INF);
add(get(u, v), get(i, j), 0);
}
}
}
dinic();
cout << n*n-m-ans << endl; //总共n*n个点,去掉m个障碍,再减去最大流
真正的总结
我们可以发现在类似这样的网格模型下,只要按题意建图,然后求最大独立集即可。(好像比小结还短)