[BZOJ 2756] 奇怪的游戏
Link:https://www.lydsy.com/JudgeOnline/problem.php?id=2756
Algorithm:
比较新颖的题目
首先发现是对矩阵中相邻两数进行操作 <-----> 想到黑白染色
于是Delta(BlackSum)=Delta(WhiteSum)
由于最后要变成同一个数X,
那么在BlackNum=WhiteNum时,
1、如果WhiteSum!=BlackSum,显然无解
2、如果WhiteSum==BlackSum时,由于矩阵能被1*2的矩形完全覆盖,那么X是否满足要求是具有单调性的
于是我们二分X,进行判断即可
在WhiteNum!=BlackNum时,
显然可以得到X∗WhiteNum−WhiteSum=X∗BlackNum−BlackSum
移项后显然可以化简出X=(BlackSum−WhiteSum) / (BlackNum−WhiteNum)
验证这个X即可
最大问题,如何判断X是否可行,
一开始得出的性质:Delta(BlackSum)=Delta(WhiteSum),符合流守恒性(流入和流出量相同)
于是我们进行可以将黑点看作一边,而将白点看作另外一边,网络流建图:
S-->白点,CAP为X-val[i][j]
黑点-->T,CAP为X-val[i][j]
相邻的白点-->黑点,CAP为INF
判断能否满流即可
Code:
#include <bits/stdc++.h> using namespace std; #define pos(x,y) (x-1)*m+y typedef pair<int,int> P; typedef long long ll; const int MAXN=50; const ll INF=1ll<<50; ll test,n,m,col[MAXN][MAXN],dat[MAXN][MAXN],iter[MAXN*MAXN],level[MAXN*MAXN]; ll sum1,sum2,cnt1,cnt2,mx,S,T; struct edge { ll to,cap,rev; }; vector<edge> G[MAXN*MAXN]; void add_edge(int from,int to,ll cap) { G[from].push_back(edge{to,cap,G[to].size()}); G[to].push_back(edge{from,0,G[from].size()-1}); } bool bfs() { memset(level,-1,sizeof(level)); queue<int> que;que.push(S); level[S]=0; while(!que.empty()) { int t=que.front();que.pop(); for(int i=0;i<G[t].size();i++) { edge e=G[t][i]; if(e.cap>0 && level[e.to]==-1) { level[e.to]=level[t]+1; que.push(e.to); } } } } ll dfs(int cur,int T,ll f) { if(cur==T) return f; for(ll &i=iter[cur];i<G[cur].size();i++) { edge &e=G[cur][i]; if(level[e.to]==level[cur]+1 && e.cap>0) { ll d=dfs(e.to,T,min(f,e.cap)); if(d>0) { e.cap-=d; G[e.to][e.rev].cap+=d; return d; } } } return 0; } ll dinic() { ll ret=0; while(true) { memset(iter,0,sizeof(iter)); bfs();if(level[T]==-1) break; ll f; while((f=dfs(S,T,INF))>0) ret+=f; //这里括号不能排错啊 } return ret; } int dx[]={0,0,1,-1}; int dy[]={1,-1,0,0}; bool check(ll tar) { S=0;T=n*m+1;ll ret=0; for(int i=0;i<MAXN*MAXN;i++) G[i].clear(); for(int i=1;i<=n;i++) //建图 for(int j=1;j<=m;j++) { if(col[i][j]) add_edge(S,pos(i,j),tar-dat[i][j]),ret+=tar-dat[i][j]; else {add_edge(pos(i,j),T,tar-dat[i][j]);continue;} for(int k=0;k<4;k++) { int fx=i+dx[k],fy=j+dy[k]; if(fx>=1 && fx<=n && fy>=1 && fy<=m) add_edge(pos(i,j),pos(fx,fy),INF); } } ll MAXFLOW=dinic(); //dinic return (ret==MAXFLOW); } int main() { cin >> test; while(test--) { cin >> n >> m;mx=0; cnt1=cnt2=sum1=sum2=0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { cin >> dat[i][j]; col[i][j]=(i+j)&1;mx=max(mx,dat[i][j]); if(col[i][j]) sum1+=dat[i][j],cnt1++; else sum2+=dat[i][j],cnt2++; } if(cnt1!=cnt2) { ll tar=(sum1-sum2)/(cnt1-cnt2); if(tar<mx){puts("-1");continue;} if(check(tar)) { cout << tar*cnt1-sum1 << endl; continue; } puts("-1"); } else { if(sum1!=sum2){puts("-1");continue;} ll l=mx,r=INF; while(l<=r) { ll mid=(l+r)>>1; if(check(mid)) r=mid-1; else l=mid+1; } if(r==INF) puts("-1"); //如果到INF都无解,则说明就是无解,要特判,但BZOJ上数据比较水(黄学长的标程是错的) else cout << 1ll*(l*cnt1-sum1) << endl; } } return 0; }
Review:
1、看到这种对相邻格子同时操作的题目,想到黑白染色
2、求极值,观察值的可行性是否具有单调性,从而能否二分
3、当数据能分为两组,且具有流守恒性时
考虑使用网络流
4、调试:
(1)有多个括号要留心每个括号的位置啊,多看几遍
while((f=dfs(S,T,INF))>0)
这里的>0一开始放到第2个括号里了,结果f每次就都是0或1了……囧,调了1h
(2)像网络流这样执行过一边就会对原数组产生影响的模块,
执行过一次后想调试/再次调用时,不能再执行一遍,将第一次结果存储即可
(3)由于模板中大部分数据都是int,如果有long long时要特别注意修改原模板中的int
惨案:
void add_edge(int from,int to,ll cap)