壹、题目
贰、思考
考虑一个点分成两个点——选择文科、选择理科,好像不行。
如何建图才能使得一个点选择文科或者理科之后可以对周围点产生影响?
或者考虑正难则反:先钦定所有人选文科,如果选择理科的代价就是减去文科以及产生共鸣时的满意度?但是又如何解决当一个十字架的人都倒戈了的情况?
唔,好像有点难,想一个简化版的问题——如果只有在同桌之间会产生共鸣呢?
我们建一个点,表示两个人都选择了文科,然后这个点分别连向两个文的情况,然后......还是不会......
叁、题解
我怎么那么菜啊......网络流建图必须对网络流的理解十分深刻,然而我太菜了什么都不知道.....深深明白自己的弱小。
这里直接引用一个大佬对这道题的说明:
我们把每一个人视作一个点 \(i\),规定:\(i\) 被割到 \(S\) 集里,代表这个人选文,\(i\) 被割到 \(T\) 集里,代表这个人选理。
将 \(S\) 连向这个点,长度为 \(art\),如果该边被割,则说明不选文,不能获得 \(art\) 的收益,可得到 \(science\) 的收益。
将这个点连向 \(T\),长度为 \(science\),如果该边被割,则说明不选理,不能获得 \(science\) 的收益,可得到 \(art\) 的收益。
那么对于同时选择的情况,我们可以新建点。
对于几个相邻的点,我们新建一个点,将 \(S\) 连向这个点,长度为同时选文可获得的收益,如果该边被割,则说明这些人不同时选文,不能获得同时选文可获得的收益。
同样,我们新建一个点,将这个点连向 \(T\),长度为同时选理可获得的收益,如果该边被割,则说明这些人不同时选理,不能获得同时选理可获得的收益,
怎么保证不割这条边则必然都选同样的科目呢?
我们可以将新建的点,向相邻的那几个点中的每一个点(或从相邻的那几个点中的每一个点向新建的点)连一条长度为 \(+\infty\) 的边,这样的边在最小割中肯定不会存在,则保证了在代表共同选择收益的边不被割时(即都选一个科目)新建的点与相邻的点在同一个点集。问题得以解决。
通过 \(\tt Dinic\) 算法求得该图的最小割,总收益减最小割即为最大收益。
肆、代码
using namespace Elaina;
const int maxn=100;
const int maxm=100;
const int maxnode=maxn*maxm;
const int inf=0x3f3f3f3f;
int n,m;
int art[maxn+5][maxm+5];
int science[maxn+5][maxm+5];
int same_art[maxn+5][maxm+5];
int same_science[maxn+5][maxm+5];
int ans=0;
inline void input(){
n=readin(1),m=readin(1);
rep(i,1,n)rep(j,1,m)
ans+=(art[i][j]=readin(1));
rep(i,1,n)rep(j,1,m)
ans+=(science[i][j]=readin(1));
rep(i,1,n)rep(j,1,m)
ans+=(same_art[i][j]=readin(1));
rep(i,1,n)rep(j,1,m)
ans+=(same_science[i][j]=readin(1));
}
struct edge{int to,nxt,c;
edge(const int T=0,const int N=0,const int C=0):to(T),nxt(N),c(C){}
}e[maxn*maxm*3*10+5];
int tail[maxn*maxm*3+5],ecnt;
int cur[maxn*maxm*3+5];
int ncnt;
inline void add_edge(const int u,const int v,const int c){
// printf("add_edge :> u == %d, v == %d, c == %d\n",u,v,c);
e[++ecnt]=edge(v,tail[u],c);tail[u]=ecnt;
}
inline void update(const int i,const int x){
e[i].c-=x,e[i^1].c+=x;
}
const int dir[][2]={{0,1},{0,-1},{-1,0},{1,0},{0,0}};
inline int getid(const int i,const int j){
return (i-1)*m+j;
}
inline int inside(const int x,const int y){
return 0<x && x<=n && 0<y && y<=m;
}
inline void build(){
memset(tail,-1,sizeof tail);
ecnt=-1;
rep(i,1,n)rep(j,1,m){
add_edge(0,getid(i,j),art[i][j]);
add_edge(getid(i,j),0,0);
add_edge(getid(i,j),n*m+1,science[i][j]);
add_edge(n*m+1,getid(i,j),0);
}
ncnt=n*m+1;
rep(i,1,n)rep(j,1,m){
++ncnt;
add_edge(0,ncnt,same_art[i][j]);
add_edge(ncnt,0,0);
// 此处 k 可以取 4, 要向 (i,j) 建边
for(int k=0;k<=4;++k){
int tx=i+dir[k][0];
int ty=j+dir[k][1];
if(!inside(tx,ty))continue;
add_edge(ncnt,getid(tx,ty),inf);
add_edge(getid(tx,ty),ncnt,0);
}
++ncnt;
add_edge(ncnt,n*m+1,same_science[i][j]);
add_edge(n*m+1,ncnt,0);
// 此处 k 可以取 4, 要向 (i,j) 建边
for(int k=0;k<=4;++k){
int tx=i+dir[k][0];
int ty=j+dir[k][1];
if(!inside(tx,ty))continue;
add_edge(getid(tx,ty),ncnt,inf);
add_edge(ncnt,getid(tx,ty),0);
}
}
}
int dis[maxn*maxm*3+5];
queue<int>Q;
inline int bfs(){
memset(dis,-1,(ncnt+1)<<2);
while(!Q.empty())Q.pop();
dis[0]=0;Q.push(0);
while(!Q.empty()){
int u=Q.front();Q.pop();
// printf("u == %d\n",u);
for(int i=tail[u],v;~i;i=e[i].nxt)if(e[i].c){
v=e[i].to;
if(dis[v]==-1){
dis[v]=dis[u]+1;
Q.push(v);
}
}
}
return dis[n*m+1]!=-1;
}
int dfs(const int u,const int flow,const int t){
if(u==t)return flow;
int rest=flow;
for(int& i=cur[u];~i;i=e[i].nxt)if(e[i].c){
int v=e[i].to;
if(dis[v]==dis[u]+1){
int k=dfs(v,Min(rest,e[i].c),t);
rest-=k;
update(i,k);
if(rest==0)break;
}
}
return flow-rest;
}
inline void dinic(){
while(bfs()){
memcpy(cur,tail,(ncnt+1)<<2);
ans=ans-dfs(0,inf,n*m+1);
}
writc(ans,'\n');
}
signed main(){
// freopen("3.in","r",stdin);
input();
build();
// printf("ncnt == %d\n",ncnt);
// printf("ecnt == %d\n",ecnt);
dinic();
return 0;
}
伍、个人理解
其实更想写的是这个部分。
网络流中,所谓限额是什么东西?就是一个东西达到了上界,而如果对于一个点,它有两种选择,我们可以将它分别向源点、汇点连边,在网络流中的含义就是——二者择其劣者选之。
这位大佬用 "点集划分" 的思路构想这道题,首先需要 "正难则反",而为什么又可能正难则反?这是建立在 "最大流=最小割" 的基础之上。在点集划分中需要将边割掉,以划分出两个不同的点集,而被割掉的边就是所谓跑满的边,这种边在割的意义下,就是最容易被割掉的,花费最少便可以舍弃掉的,而在 "反" 之后,就是最劣的边。而在割中,连接一个 \(+\infty\) 的边意味着什么?这条边永远不会被割掉,最小割不可能蠢到去割 \(+\infty\) 的边吧......在点集划分的意义下,就是强行将边两端的点绑在了一起,它们永远同属一个点集。
写完都还有点模糊,还是得体会一下网络流啊......