luogu P3756 [CQOI2017]老C的方块(最小割&四色染色)
题目大意
老C是个程序员。
作为一个懒惰的程序员,老C经常在电脑上玩方块游戏消磨时间。游戏被限定在一个由小方格排成的R行C列网格上,如果两个小方格有公共的边,就称它们是相邻的,而且有些相邻的小方格之间的公共边比较特殊。特殊的公共边排列得有很强的规律。首先规定,第1行的前两个小方格之间的边是特殊边。然后,特殊边在水平方向上每4个小方格为一个周期,在竖直方向上每2个小方格为一个周期。所有的奇数列与下一列之间都有特殊边,且所在行的编号从左到右奇偶交替。
下图所示是一个R=C=8的网格,蓝色标注的边是特殊边。首先,在第1行,第1列和第2列之间有一条特殊边。因为竖直方向周期为2,所以所有的奇数行,第1列和第2列之间都有特殊边。因为水平方向周期为4,所以所有奇数行的第5列和第6列之间也有特殊边,如果网格足够大,所有奇数行的第9列和第10列、第13列和第14列之间都有特殊边。因为所有的奇数列和下一列之间都有特殊边,所以第3列和第4列、第7列和第8列之间也有特殊边,而所在行的编号从左到右奇偶交替,所以它们的特殊边在偶数行。如果网格的规模更大,我们可以用同样的方法找出所有的特殊边。
网格的每个小方格刚好可以放入一个小方块,在游戏的一开始,有些小方格已经放上了小方块,另外的小方格没有放。老C很讨厌下图所示的图形,如果他发现有一些小方块排列成了它讨厌的形状(特殊边的位置也要如图中所示),就很容易弃疗,即使是经过任意次旋转、翻转后排列成讨厌的形状,老C也同样容易弃疗。
为了防止弃疗,老C决定趁自己还没有弃疗,赶紧移除一些格子里小方块,使得剩下的小方块不能构成它讨厌的形状。但是游戏里每移除一个方块都是要花费一些金币的,每个方块需要花费的金币有多有少参差不齐。老C当然希望尽可能少的使用游戏里的金币,但是最少要花费多少金币呢?老C懒得思考,就把这个问题交给你了。
解题思路
对原图染上四种颜色,以一二行的前8个为例,后面的以此为周期重复
其中灰色的线就是原题目中所说的直线。
在最小割中同时满足就是并联,满足任一就是串联,考虑建下面的图
其中黄色格子和源点之间的边的容量为 黄色点的权值,黄色与红色点之间的容量为inf,红色与蓝色点之间的容量为红蓝颜色中权值较小的权值,蓝绿之间的权值为inf,绿色与汇点直接的容量为绿色的点的权值。
最后跑出最小割就是答案
AC代码
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+5;
const int maxm=5e6+5;
const LL inf=1e12;
struct Edge{
int to,nxt;
LL cap,flow;
}edge[maxm];
int tol;
int head[maxn];
inline void init(){
tol=2;
memset(head,-1,sizeof(head));
}
inline void AddEdge(int u,int v,LL w,int rw=0){
edge[tol].to=v;edge[tol].cap=w;edge[tol].flow=0;
edge[tol].nxt=head[u];head[u]=tol++;
edge[tol].to=u;edge[tol].cap=rw;edge[tol].flow=0;
edge[tol].nxt=head[v];head[v]=tol++;
}
int Q[maxn];
int dep[maxn],cur[maxn],sta[maxn];
inline bool bfs(int s,int t,int n){
int front=0,tail=0;
memset(dep,-1,sizeof(dep[0])*(n+1));
dep[s]=0;
Q[tail++]=s;
while(front<tail){
int u=Q[front++];
for(register int i=head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].to;
if(edge[i].cap>edge[i].flow&&dep[v]==-1){
dep[v]=dep[u]+1;
if(v==t) return true;
Q[tail++]=v;
}
}
}
return false;
}
inline LL dinic(int s,int t,int n){
LL maxflow=0;
while(bfs(s,t,n)){
for(register int i=0;i<n;++i) cur[i]=head[i];
int u=s,tail=0;
while(cur[s]!=-1){
if(u==t){
LL tp=inf;
for(register int i=tail-1;i>=0;--i)
{
tp=min(tp,edge[sta[i]].cap-edge[sta[i]].flow);
}
maxflow+=tp;
for(register int i=tail-1;i>=0;--i){
edge[sta[i]].flow+=tp;
edge[sta[i]^1].flow-=tp;
if(edge[sta[i]].cap-edge[sta[i]].flow==0) tail=i;
}
u=edge[sta[tail]^1].to;
}
else if(cur[u]!=-1&&edge[cur[u]].cap>edge[cur[u]].flow&&dep[u]+1==dep[edge[cur[u]].to]){
sta[tail++]=cur[u];
u=edge[cur[u]].to;
}
else{
while(u!=s&&cur[u]==-1) u=edge[sta[--tail]^1].to;
cur[u] = edge [cur[u]].nxt;
}
}
}
return maxflow;
}
struct node{
int x,y,w,id;
node(){}
node(int x,int y,int w,int id):x(x),y(y),w(w),id(id){}
}block[100005];
typedef pair<int,int> pii;
map<pii,int> ids;
int col[100005];
inline int color_maker(int x,int y)
{
//1 red 2 blue 3 green 4 yellow
if(y&1)
{
if(x%4==1) return 1;
if(x%4==2) return 2;
if(x%4==3) return 3;
if(x%4==0) return 4;
}
else
{
if(x%4==1) return 4;
if(x%4==2) return 3;
if(x%4==3) return 2;
if(x%4==0) return 1;
}
}
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int main()
{
int c,r,n;
init();
scanf("%d%d%d",&c,&r,&n);
ids.clear();
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&block[i].x,&block[i].y,&block[i].w);
block[i].id=i;
col[i]=color_maker(block[i].x,block[i].y);
ids[pii(block[i].x,block[i].y)]=i;
}
int t=n+1;
int s=0;
for(int i=1;i<=n;i++)
{
int x=block[i].x,y=block[i].y;
if(col[i]==1)
{
for(int j=0;j<4;j++)
{
int xx=x+dir[j][0],yy=y+dir[j][1];
if(!ids.count(pii(xx,yy))) continue;
int idnew=ids[pii(xx,yy)];
if(col[idnew]==4) AddEdge(idnew,i,inf);
if(col[idnew]==2) AddEdge(i,idnew,min(block[i].w,block[idnew].w));
}
}
if(col[i]==2)
{
for(int j=0;j<4;j++)
{
int xx=x+dir[j][0],yy=y+dir[j][1];
if(!ids.count(pii(xx,yy))) continue;
int idnew=ids[pii(xx,yy)];
if(col[idnew]==3) AddEdge(i,idnew,inf);
}
}
if(col[i]==3) AddEdge(i,t,block[i].w);
if(col[i]==4) AddEdge(s,i,block[i].w);
}
printf("%d\n",dinic(s,t,t+1));
}
if(col[idnew]==3) AddEdge(i,idnew,inf);
}
}
if(col[i]==3) AddEdge(i,t,block[i].w);
if(col[i]==4) AddEdge(s,i,block[i].w);
}
printf("%d\n",dinic(s,t,t+1));
}