题目描述:
小P所在的班级要进行文理分科。他的班级可以用一个n*m的矩阵进行描述,每个格子代表一个同学的座位。每位同学必须从文科和理科中选择一科。同学们在选择科目的时候会获得一个满意值。满意值按如下的方式得到:
-
如果第i行第秒J的同学选择了文科,则他将获得art[i][j]的满意值,如果选择理科,将得到science[i][j]的满意值。
-
如果第i行第J列的同学选择了文科,并且他相邻(两个格子相邻当且仅当它们拥有一条相同的边)的同学全部选择了文科,则他会更开心,所以会增加same_art[i][j]的满意值。
-
如果第i行第j列的同学选择了理科,并且他相邻的同学全部选择了理科,则增加same_science[i][j]的满意值。
小P想知道,大家应该如何选择,才能使所有人的满意值之和最大。请告诉他这个最大值。
输入输出样例
输入样例:
3 4
13 2 4 13
7 13 8 12
18 17 0 5
8 13 15 4
11 3 8 11
11 18 6 5
1 2 3 4
4 2 3 2
3 1 0 4
3 2 3 2
0 2 2 1
0 2 4 4
输出样例:
152
这道题看了很久,在错(D)误(P)的道路上一路狂飙。试了好几种状态,每种状态想了好几种状态转移方程,就是找不到能够证明正确性的解法。厚着脸皮去问大佬,大佬回答:这难道不是傻逼最小割吗?
听了之后我恍然大悟,立马(花了1个小时)找到了正确的模型和正确的建边方式。
把每个人当做一个点那么选文或者选理科就是两个与众不同的集合
首先只考虑前两种情况,即对于每个人只选文科或者选理科的情况。
从源点向每个点连一条边,边权为art,从汇点向每个点连一条边,边权为science。单看这个图直接跑网络流会产生最小割。如果一条边被割断了就表示不选择这个情况。
也就是说:
若源点连出的边被割断了,则表示我不选择文科。同理,若连到汇点的边被割断了就表示不选择理科。
那么前两种情况就处理完了。剩下的就是处理周围4个二货同学的事情了。
对于一个点周围的几个相邻的点的情况,我们新建一个点,将S连向这个点,长度为同时选文可获得的收益,如果该边被割,则说明这些人不同时选文,不能获得同时选文可获得的收益。理科的情况以此类推。
那么怎么保证如果这几个人选相同的科目时能够不割断这个边呢?
从周围这个点向周围的同学连一条INF的边,那么这种情况下都选同一科时,新边肯定不会被割断,就保证了网络流当中的割是使剩下的图满足条件的最小值。
理科的建图就是相反的(从周围的同学连向自己,具体理科建图方式请看代码)。
这个时候网络流中的割就是舍去之后满足条件的需要舍去的值(读着有点拗口)。
根据最大流最小割定理。求出网络流图中的最大流,就是最小割。
那么用总的开心值减去最小割就是最大流了。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define num(i,j) (((i)-1)*m+(j))
using namespace std;
const int maxn=2000010-3;
struct edge
{
int v,w,next,fxb;
}e[maxn*10];
int pre[maxn],head[maxn],dis[maxn],num=1,S=1,T;
inline void add_Edge(int u,int v,int w)
{
e[num].v=v;e[num].w=w;e[num].next=head[u];e[num].fxb=num+1;head[u]=num;num++;
e[num].v=u;e[num].w=0;e[num].next=head[v];e[num].fxb=num-1;head[v]=num;num++;
}
inline bool bfs()
{
memset(dis,-1,sizeof(dis));
queue<int> q;
dis[S]=1;
q.push(S);
while(!q.empty())
{
int u=q.front();
q.pop();
for(register int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==-1&&e[i].w>0)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[T]==-1)
return false;
else
return true;
}
inline int dfs(int x,int f)
{
int temp,total=0;
if(x==T) return f;
for(register int i=pre[x];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]!=dis[x]+1) continue;
if(e[i].w<=0)continue;
temp=dfs(v,min(f, e[i].w));
total+=temp;f-=temp;
e[i].w-=temp;e[e[i].fxb].w+=temp;pre[x]=i;
if(!f)return total;
}
return total;
}
#define MAT 510
int c1[4]={0,0,1,-1};
int c2[4]={1,-1,0,0};
int n,m;
inline bool check(int x,int y)
{
return x>0&&x<n&&y<m&&y>1;
}
int main()
{
int sum=0,newNode,sa,id;
scanf("%d%d",&n,&m);
S=n*m+1;T=n*m+2,newNode=n*m+2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&sa);
id=num(i,j);
sum+=sa;
add_Edge(S,id,sa);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&sa);
id=num(i,j);
sum+=sa;
add_Edge(id,T,sa);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&sa);
id=num(i,j);
newNode++;
sum+=sa;
add_Edge(S,newNode,sa);
add_Edge(newNode,id,0x3f3f3f3f);
for(int k=0;k<4;k++)
if(i+c1[k]>=1&&i+c1[k]<=n&&j+c2[k]>=1&&j+c2[k]<=m)
add_Edge(newNode,num(i+c1[k],j+c2[k]),0x3f3f3f3f);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&sa);
id=num(i,j);
newNode++;
sum+=sa;
add_Edge(newNode,T,sa);
add_Edge(id,newNode,0x3f3f3f3f);
for(int k=0;k<4;k++)
if( i+c1[k]>=1&&i+c1[k]<=n&&j+c2[k]>=1&&j+c2[k]<=m)
add_Edge(num(i+c1[k],j+c2[k]),newNode,0x3f3f3f3f);
}
int f=0,mincut=0;
while(bfs())
{
memcpy(pre, head, sizeof(head));
while(f=dfs(S,0x3f3f3f3f))mincut+=f;
}
printf("%d\n",sum-mincut);
return 0;
}
这个网络流是我直接用的很久之前的板,毕竟很久都没有打过了