思路:这题主要是建图搞了好久,以前没判断过满流,所以又看了这个知识点,然后才发现自己的最小费用最大流在求满流的时候有bug,正好改了过来。
建图:开始看题解知道这题是最小费用最大流,然后没看解释就做了。然后自己建的图没得求出答案,然后想了好久也没发现哪里错。然后看了官方题解,发现自己的建图和官方差太大了。可能是受昨天做POJ最小费用建图的影响吧。官方的建太符合题目意思了,只能说,我还看了好久建图才理解的。
构造二部图,X部有N*M个节点,源点向X部每个节点连一条边,流量1,费用0;Y部有N*M个节点,每个节点向汇点连一条边,流量1,费用0;如果X部的节点x可以在一步之内到达Y部的节点y(即题目中说的可以向右或者向下走),那么就连边x->y,费用为从x格子到y格子的花费能量减去得到的能量(这题求最大费用最大流,所以是负数),流量1;再在X部增加一个新的节点,表示可以从任意节点出发K次(这个我还理解了很久才知道的,原因是比如第三个样例,1和2的点每点只能一步就退出了,所以新建的这个点是为了第三个样例这种情况存在的),源点向其连边,费用0,流量K,这个点向Y部每个点连边,费用0,流量1,最这个图跑最小费用最大流;如果满流就是存在解,反之不存在,最小费用的相反数就是可以获得的最大能量。
代码解释:b[i][j]*2-1表示每个点拆点后这个点属于左部图,b[i][j]*2表示右部图。
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<bitset>
#define mem(a,b) memset(a,b,sizeof(a))
#define lson i<<1,l,mid
#define rson i<<1|1,mid+1,r
#define llson j<<1,l,mid
#define rrson j<<1|1,mid+1,r
#define INF 0x7fffffff
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
#define maxn 20005
struct
{
int v,w,c,next,re;
//re记录逆边的下标,c是费用,w是流量
} e[maxn];
int sink,cnt,flow,minflow;
int head[maxn],que[maxn*10],pre[maxn],dis[maxn];
bool vis[maxn];
void add(int u, int v, int w, int c)
{
e[cnt].v=v,e[cnt].w=w,e[cnt].c=c;
e[cnt].next=head[u];
e[cnt].re=cnt+1,head[u]=cnt++;
e[cnt].v=u,e[cnt].w=0,e[cnt].c=-c;
e[cnt].next=head[v];
e[cnt].re=cnt-1,head[v]=cnt++;
}
bool spfa()
{
int i, l = 0, r = 1;
for(i = 0; i <= sink; i ++)
dis[i] = INF,vis[i] = false;
dis[0]=0,que[0]=0,minflow=INF,vis[0]=true;
while(l<r)
{
int u=que[l++];
for(i=head[u]; i!=-1; i=e[i].next)
{
int v = e[i].v;
if(e[i].w&&dis[v]>dis[u]+e[i].c)
{
dis[v] = dis[u] + e[i].c;
minflow=min(minflow,e[i].w);
pre[v] = i;
if(!vis[v])
{
vis[v] = true;
que[r++] = v;
}
}
}
vis[u] = false;
}
return dis[sink]!=INF;
}
int change()
{
int i,p;
for(i=sink; i!=0; i=e[e[p].re].v)
{
p=pre[i];
e[p].w-=minflow;
e[e[p].re].w+=minflow;
}
flow+=minflow;
return minflow*dis[sink];
}
int EK()
{
int sum=0;
while(spfa()) sum+=change();
return sum;
}
void init()
{
mem(head,-1),mem(pre,0),cnt=0,flow=0;
}
int main()
{
//freopen("1.txt","r",stdin);
int t,ii=1;
scanf("%d",&t);
while(t--)
{
init();
int N,M,K,i,j,k,z,tmp=0,b[10][11];
char s[10][11];
scanf("%d%d%d",&N,&M,&K);
for(i=0; i<N; i++) scanf("%s",s[i]);
for(i=0; i<N; i++)
for(j=0; j<M; j++)
b[i][j]=++tmp;
for(i=0; i<N; i++)
for(j=0; j<M; j++)
{
for(k=j+1; k<M; k++)//向右走
{
z=0;
if(s[i][j]==s[i][k]) z=s[i][j]-'0';
add(b[i][j]*2-1,b[i][k]*2,1,k-j-1-z);
}
for(k=i+1;k<N;k++)//向下走
{
z=0;
if(s[i][j]==s[k][j]) z=s[i][j]-'0';
add(b[i][j]*2-1,b[k][j]*2,1,k-i-1-z);
}
}
int New=tmp*2+1;
add(0,New,K,0);//源点与左部新建结点连
for(i=0;i<N;i++)
for(j=0;j<M;j++)
{
add(0,b[i][j]*2-1,1,0);//源点与左部每个点都相连
add(New,b[i][j]*2,1,0);//新建结点与右部每个结点连
}
sink=tmp*2+2;
for(i=0;i<N;i++)
for(j=0;j<M;j++)//汇点与右部每个点也连
add(b[i][j]*2,sink,1,0);
int ans=EK();
printf("Case %d : ",ii++);
if(flow!=N*M) puts("-1");
else printf("%d\n",-ans);
}
return 0;
}