HDU 3338 Kakuro Extension(最大流,拆点)

探讨了一种基于数独的Kakuro变形问题,利用最大流算法解决该问题。通过对网格进行分析,建立了复杂的图模型,并通过最大流算法找到了有效的解决方案。

HDU 3338 Kakuro Extension

If you solved problem like this, forget it.Because you need to use a completely different algorithm to solve the following one.
Kakuro puzzle is played on a grid of "black" and "white" cells. Apart from the top row and leftmost column which are entirely black, the grid has some amount of white cells which form "runs" and some amount of black cells. "Run" is a vertical or horizontal maximal one-lined block of adjacent white cells. Each row and column of the puzzle can contain more than one "run". Every white cell belongs to exactly two runs ― one horizontal and one vertical run. Each horizontal "run" always has a number in the black half-cell to its immediate left, and each vertical "run" always has a number in the black half-cell immediately above it. These numbers are located in "black" cells and are called "clues".The rules of the puzzle are simple:

1.place a single digit from 1 to 9 in each "white" cell
2.for all runs, the sum of all digits in a "run" must match the clue associated with the "run"

Given the grid, your task is to find a solution for the puzzle.   

      

 

Input
The first line of input contains two integers n and m (2 ≤ n,m ≤ 100) ― the number of rows and columns correspondingly. Each of the next n lines contains descriptions of m cells. Each cell description is one of the following 7-character strings:
.......― "white" cell;
XXXXXXX― "black" cell with no clues;
AAA\BBB― "black" cell with one or two clues. AAA is either a 3-digit clue for the corresponding vertical run, or XXX if there is no associated vertical run. BBB is either a 3-digit clue for the corresponding horizontal run, or XXX if there is no associated horizontal run.
The first row and the first column of the grid will never have any white cells. The given grid will have at least one "white" cell.It is guaranteed that the given puzzle has at least one solution.
Output
Print n lines to the output with m cells in each line. For every "black" cell print '_' (underscore), for every "white" cell print the corresponding digit from the solution. Delimit cells with a single space, so that each row consists of 2m-1 characters.If there are many solutions, you may output any of them. Sample Input 6 6
XXXXXXX XXXXXXX 028\XXX 017\XXX 028\XXX XXXXXXX
XXXXXXX 022\022 ....... ....... ....... 010\XXX
XXX\034 ....... ....... ....... ....... .......
XXX\014 ....... ....... 016\013 ....... .......
XXX\022 ....... ....... ....... ....... XXXXXXX
XXXXXXX XXX\016 ....... ....... XXXXXXX XXXXXXX
5 8
XXXXXXX 001\XXX 020\XXX 027\XXX 021\XXX 028\XXX 014\XXX 024\XXX
XXX\035 ....... ....... ....... ....... ....... ....... .......
XXXXXXX 007\034 ....... ....... ....... ....... ....... .......
XXX\043 ....... ....... ....... ....... ....... ....... .......
XXX\030 ....... ....... ....... ....... ....... ....... XXXXXXX
Sample Output
_ _ _ _ _ _
_ _ 5 8 9 _
_ 7 6 9 8 4
_ 6 8 _ 7 6
_ 9 2 7 4 _
_ _ 7 9 _ _
_ _ _ _ _ _ _ _
_ 1 9 9 1 1 8 6
_ _ 1 7 7 9 1 9
_ 1 3 9 9 9 3 9
_ 6 7 2 4 9 2 _


       题目的意思就是一个数独游戏的变形,黑色格子右侧的数字代表沿行方向向右,从该黑色格子开始,到下一个黑色格子结束,白色格子里数字之和;左侧的数字代表沿列方向向下,从该黑色格子开始,到下一个黑色格子结束,白色格子里数字之和。注意每行、每列的数字可以重复,范围是1~9。        输出就是填格子的情况。        给其他人看了,好多人说一眼觉得是DP,不过因为是在刷专题,我直接就往最大流想了,减少了很多思考过程…        基本想法就是先统计所有白色格子里数字的总和,设置一个源点,向每个约束行数字和的格子建立一条权值为该格子约束和的边(视作行流入点);再从行流入点向其约束的格子建立一条权值为9的边;最后从每个被列和约束的格子向列约束格子建立一条权值为9的边(视作列流出点),并从每个列流出点向汇点引一条不限流量的边。        最后当该图从源点向汇点的各边流量满足最大流=数字总和时,完成了行流入=列流出的约束,输出各个点流出边流量,就是填入的数字。        具体实现时,还有两个要解决的问题:        (1)每个格子里数字的范围是1~9,而弧流量的范围的下限是0,因此直接按上面的想法输出,不能避免弧流量=0情况的出现。解决办法是把边权值变为8,而源点向各行约束点流入的权值为约束数字和-约束格子数,最后输出弧流量时每个流量+1;        (2)对于既约束了行又约束了列的格子,由于源点和其约束的列点都对其有输入,会产生干扰,所以需要拆点,将其拆为一个行流入点和一个列流出点分别保存和建边(做的时候一直过不去样例就是因为没考虑这个,学长说做网络流的题一定要经常考虑拆点的情况);

#include <cstdio>
#include <cstring>
#include <climits>
#include <algorithm>
#define MAXN 20000
#define maxn 110
int head[MAXN],cnt,ans;
int gap[MAXN],curedge[MAXN],d[MAXN],pre[MAXN];
// gap:统计高度数量数组,d:距离标号数组;
// curedges:当前弧数组;pre:前驱数组
char in[maxn][maxn][10];
int flow,maxs=0,doublenum=0;
struct node{
    int cap,to;
    int next;
}edge[1000000];
void initi(){
    //memset(in,'\0',sizeof(in));
    memset(d,0,sizeof(d));
    memset(gap,0,sizeof(gap));
    memset(pre,-1,sizeof(pre));
    memset(head,-1,sizeof(head));
    ans=0;//初始化最大流为0
    cnt=0;
}
void addedge(int a,int b,int c){//有向图加边
    edge[cnt].to=b,edge[cnt].cap=c;
    edge[cnt].next=head[a],head[a]=cnt++;
    edge[cnt].to=a,edge[cnt].cap=0;
    edge[cnt].next=head[b],head[b]=cnt++;
}
int max_flow(int start,int end,int n){
    int i,u,tmp,neck;
    for(i=1;i<=n;i++)
        curedge[i]=head[i];//初始化当前弧为第一条邻接边
    gap[0]=n;
    u=start;
    while(d[start]<n){//当d[start]>=n,网络中肯定出现了gap
        if(u==end){//增广成功,寻找瓶颈边
            int min_flow=INT_MAX;
            for(i=start;i!=end;i=edge[curedge[i]].to){
                if(min_flow>edge[curedge[i]].cap){
                    neck=i;
                    min_flow=edge[curedge[i]].cap;
                }
            }
            for(i=start;i!=end;i=edge[curedge[i]].to){//更新边与回退边的流量
                tmp=curedge[i];
                edge[tmp].cap-=min_flow;
                edge[tmp^1].cap+=min_flow;//^1:偶数+1,奇数-1
            }
            ans+=min_flow;
            u=neck;//下次增广从瓶颈边开始
        }
        for(i=curedge[u];i!=-1;i=edge[i].next)
            if(edge[i].cap&&d[u]==d[edge[i].to]+1)//寻找可行弧
                break;
			if(i!=-1){
				curedge[u]=i;
				pre[edge[i].to]=u;
				u=edge[i].to;
			}
			else{
				if(--gap[d[u]]==0)
					break;
				curedge[u]=head[u];
				for(tmp=n,i=head[u];i!=-1;i=edge[i].next)
					if(edge[i].cap)
						tmp=std::min(tmp,d[edge[i].to]);
				d[u]=tmp+1;
				++gap[d[u]];
				if(u!=start)
					u=pre[u];//重标号并且从当前点前驱重新增广
        }
    }
    return ans;
}
int input(int N,int M){
    int i,j,k,L=0,R=0,num,counts;
    int l,r;
    for(i=1;i<=N;i++){
        for(j=1;j<=M;j++){
            for(k=0;k<7;k++){
                scanf("%c",&in[i][j][k]);
            }
            getchar();
        }
    }
    for(i=1;i<=N;i++){
        for(j=1;j<=M;j++){
            if(in[i][j][3]=='X'||in[i][j][3]=='.')
                continue;
            if(in[i][j][0]!='X'){//列方向流出
                //printf("out:(%d,%d)\n",i,j);
                num=0;
                counts=0;
                for(k=0;k<3;k++){
                    num=num*10+(int)in[i][j][k]-48;
                }
                R+=num;
                for(r=i+1;r<=N;r++){
                    if(in[r][j][0]!='.')
                        break;
                    addedge((r-1)*M+j+1,(i-1)*M+j+1,8);//各点接列出点
                    counts++;
                }
                addedge((i-1)*M+j+1,N*M+2,num-counts);//列出点接e
            }
            if(in[i][j][0]!='X'&&in[i][j][4]!='X'){//对于既是入又是出的点,拆点额外处理入的情况
                //printf("both:(%d,%d)\n",i,j);
                num=0;
                counts=0;
                for(k=4;k<7;k++){
                    num=num*10+(int)in[i][j][k]-48;
                }
                L+=num;
                for(l=j+1;l<=M;l++){
                    if(in[i][l][0]!='.')
                        break;
                    addedge((i-1)*M+j+1+N*M+2,(i-1)*M+l+1,8);//行起点(拆后)接各点
                    counts++;
                }
                if((i-1)*M+j+1+N*M+2>maxs)
                    maxs=(i-1)*M+j+1+N*M+2;
                doublenum++;
                addedge(1,(i-1)*M+j+1+N*M+2,num-counts);//行起点(拆后)接s
                flow+=counts;
                continue;
            }
            if(in[i][j][4]!='X'){//行方向
                //printf("in:(%d,%d)\n",i,j);
                num=0;
                counts=0;
                for(k=4;k<7;k++){
                    num=num*10+(int)in[i][j][k]-48;
                }
                L+=num;
                for(l=j+1;l<=M;l++){
                    if(in[i][l][0]!='.')
                        break;
                    addedge((i-1)*M+j+1,(i-1)*M+l+1,8);//行起点接各点
                    counts++;
                }
                addedge(1,(i-1)*M+j+1,num-counts);//行起点接s
                flow+=counts;
            }
        }
    }
    addedge(0,1,L);//*
    return 0;
}
int output(int N,int M){
    int i,j,k;
    for(i=1;i<=N;i++){
        for(j=1;j<=M;j++){
            if(in[i][j][3]!='.'){
                if(j==M)
                    printf("_");
                else
                    printf("_ ");
            }
            else{
                if(j==M){
                    printf("%d",edge[head[(i-1)*M+j+1]].cap+1);
                }
                else{
                    printf("%d ",edge[head[(i-1)*M+j+1]].cap+1);
                }
            }
        }
        printf("\n");
    }
    return 0;
}
int main(){
    int N,M,i,j,n;
    while(scanf("%d %d",&N,&M)!=EOF){
    flow=0;
    n=0;
    getchar();
    initi();
    input(N,M);
    for(i=1;i<=N;i++){
        for(j=1;j<=M;j++){
            if(in[i][j][3]!='\\'&&in[i][j][3]!='.')
                n++;
        }
    }
    n=N*M-n+3;//*
    flow+=max_flow(1,N*M+2,n+doublenum);//*
    //printf("flow=%d\n",flow);
    output(N,M);
    }
	return 0;
}
//对于既是行流入点又是列流出点的点,要拆点


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值