题解 - P2045 方格取数加强版
题目描述
给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= 1000)现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要求K次所达到的方格的数的和最大
输入输出格式
输入格式:
第一行两个数n,k(1<=n<=50, 0<=k<=10)
接下来n行,每行n个数,分别表示矩阵的每个格子的数
输出格式:
一个数,为最大和
输入输出样例
输入样例#1:
3 1
1 2 3
0 2 1
1 4 2
输出样例#1:
11
说明
每个格子中的数不超过1000
网络流总能干一些奇奇怪怪的东西。。。
总结一下,比如要求次数,有方向性的东西。。。
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
int S,T,ans=0,n,bi=1,k,fl[100005],head[100005],nex[100005],go[100005],w[100005],d[100005],dis[100005],pre[100005],vis[100005],last[100005];
void add(int x,int y,int v,int dd)
{
nex[++bi]=head[x];
head[x]=bi;
w[bi]=v;
d[bi]=dd;
go[bi]=y;
}
bool spfa()
{
memset(dis,0x7f,sizeof(dis));
memset(fl,0x7f,sizeof(fl));
memset(vis,0,sizeof(vis));
queue <int> q;
pre[T]=-1;
dis[S]=0;
vis[S]=-1;
q.push(S);
while(!q.empty())
{
int u=q.front();
// cout<<u<<endl;
q.pop();
vis[u]=0;
for(int i=head[u];i;i=nex[i])
{
int v=go[i];
if(w[i]>0&&dis[v]>dis[u]+d[i])
{
dis[v]=dis[u]+d[i];
pre[v]=u;
last[v]=i;
fl[v]=min(fl[u],w[i]);
if(!vis[v])
{
vis[v]=-1;
q.push(v);
}
}
}
}
if(pre[T]==-1) return 0;
return 1;
}
int num(int i,int j)//实用小技巧
{
return 2*((i-1)*n+j);
}
int main()
{
S=0;
cin>>n>>k;
T=n*n*2+1;//每个点分裂成两个
add(0,num(1,1)-1,k,0);//因为可以走k次,故流量为k
add(num(1,1)-1,0,0,0);
add(num(n,n),T,k,0);//超级汇点
add(T,num(n,n),0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
x=-x;
add(num(i,j)-1,num(i,j),1,x);//自己跟自己连一条流量为1,费用为x的边,保证流过一次,费用+;
add(num(i,j),num(i,j)-1,0,-x);
add(num(i,j)-1,num(i,j),1e9+7,0);//再连一条联通就行的边
add(num(i,j),num(i,j)-1,0,0);
if(i!=1)
{
add(num(i-1,j),num(i,j)-1,1e9,0);//联通即可
add(num(i,j)-1,num(i-1,j),0,0);
}
if(j!=1)
{
add(num(i,j-1),num(i,j)-1,1e9+7,0);//联通即可
add(num(i,j)-1,num(i,j-1),0,0);
}
}
while(spfa())//板子
{
ans+=(fl[T]*dis[T]);
int now=T;
while(now!=S)
{
w[last[now]]-=fl[T];
w[last[now]^1]+=fl[T];
now=pre[now];
}
}
cout<<-ans<<endl;//我们是取反后跑最小费用流,相当于最大费用流,所以输出要取反
return 0;
}