hdu3338——建图+最大流

本文详细解析了Kakuro谜题的算法实现,通过构建无源无汇有容量下界的网络,利用最大流算法解决谜题。文章介绍了如何建立网络模型,包括节点和边的定义,并给出了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3338

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.
              
        Picture of the first sample input            Picture of the first sample output

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 _

题目翻译:

如果你解决了‎‎这样的‎‎问题,那就忘了它吧。因为您需要使用完全不同的算法来解决以下算法。‎
‎Kakuro谜题在"黑色"和"白色"细胞的网格上播放。除了上行和最左边的完全黑色的列外,网格还有一定数量的白细胞,它们形成"运行"和一定数量的黑色单元格。"运行"是相邻白细胞的垂直或水平最大单行线块。谜题的每一行和一列可以包含多个"运行"。每个白细胞只属于两个运行 - 一个水平和一个垂直运行。每个水平"运行"始终在黑色半单元格中在其左上方有一个数字,并且每个垂直"运行"在紧接它上方的黑色半单元格中始终有一个数字。这些数字位于"黑色"单元格中,称为"线索"。谜题‎

‎的规则很简单:1.在每个"白色"单元格‎
‎2 中放置一个从 1 到 9 的一位数字。对于所有运行,"运行"中所有数字的总和必须与与"run"‎

‎关联的线索匹配 给定网格,您的任务就是找到解决这个谜题的办法。‎
‎第‎‎个样本输入的图片‎
‎第一个样本输出的图片‎

‎输入‎

‎第一行输入包含两个整数 n 和 m (2 = n,m = 100) = 相应的行数和列数。接下来的 n 行中的每行都包含 m 单元格的说明。每个单元格描述是以下 7 个字符字符串之一:...= ‎
‎ ‎
‎"白色"单元格;‎
‎XXXXXXX= "黑色"细胞,没有线索;‎
‎AAA_BBB_"黑色"单元格,有一个或两个线索。AAA 是相应垂直运行的 3 位线索,如果没有关联的垂直运行,则为 XXX。BBB 是相应水平运行的 3 位线索,如果没有关联的水平运行,则为 XXX。‎
‎网格的第一行和第一列将永远不会有任何白单元格。给定的网格将至少有一个"白色"单元格。保证给定的谜题至少有一个解决方案。‎

‎输出‎

‎将 n 行打印到输出中,每行中都有 m 单元格。对于每个"黑色"单元格打印"+"(下划线),每个"白色"单元格打印解决方案中的相应数字。用单个空格分隔单元格,以便每行由 2m-1 个字符组成。如果有许多解决方案,您可以输出其中任何一个解决方案。

 

类似与扫雷游戏,先给你一个图,有黑和白,黑色的格子里面有两个数值,左边的值是黑格子当前列开始下面的白色格子之和,右边的值则表示当前列开始右边的白色格子之和。求满足所有黑格子的白格子的合理安排。

之后就是怎么建图了。
首先建图里面需要有刘汝佳白书里的无源无汇有容量下界网络的最大流(P366)的知识,可以看一下,估计第一次也不是很懂,看完之后可以再看下面的建图,估计会懂很多。

总的思路是S->带有行值的黑格子->白色格子->带有列值的黑格子->T。

从S到带有行值的黑格子建的边的容量为行值,不过这个行值要处理一下,因为行值的下限为1,我们可以先把它默认为8,最后处理一下就可以了,所以这里行值为行值减去当前行右边的白格子数目。

之后是带有行值的黑格子到白色格子,它们的容量为8,

之后的都跟上面一样。

具体过程参考代码。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 20500;
const int INF = 0x3f3f3f3f;
int n,m;
struct Edge
{
    int from,to,cap,flow;
};
vector<int> v[maxn];
vector<Edge> e;
int dis[maxn];  //构建层次图
int cur[maxn];
void init(int n){
	for(int i=0;i<=n;i++)
            v[i].clear();
    e.clear();
}
void addedge(int from,int to,int cap)
{
    e.push_back((Edge){from,to,cap,0});
    e.push_back((Edge){to,from,0,0});
    int m=e.size();
    v[from].push_back(m-2);
    v[to].push_back(m-1);
}
bool bfs(int s,int t)
{
    memset(dis,-1,sizeof(dis));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=0;i<v[x].size();i++)
        {
            Edge tmp = e[v[x][i]];
            if(dis[tmp.to]<0 && tmp.cap>tmp.flow)  //第二个条件保证
            {
                dis[tmp.to]=dis[x]+1;
                q.push(tmp.to);
            }
        }
    }
    if(dis[t]>0)
        return true;
    return false;
}
int dfs(int o,int t,int f)
{
    if(o==t || f==0)  //优化
        return f;
    int a = 0,ans=0;
    for(int &i=cur[o];i<v[o].size();i++) //注意前面 ’&‘,很重要的优化
    {
        Edge &tmp = e[v[o][i]];
        if(dis[tmp.to]==(dis[o]+1) && (a = dfs(tmp.to,t,min(f,tmp.cap-tmp.flow)))>0)
        {
            tmp.flow+=a;
            e[v[o][i]^1].flow-=a; //存图方式
            ans+=a;
            f-=a;
            if(f==0)  //注意优化
                break;
        }
    }
    return ans;  //优化
}
int dinic(int s,int t)
{
    int ans=0;
    while(bfs(s,t))
    {
        memset(cur,0,sizeof(cur));
        int tm=dfs(s,t,INF);
        ans+=tm;
    }
    return ans;
}
int map[250][250];
struct Node
{
    int x,y,z;//分别表示 x 坐标,y 坐标 以及该行(列)白色格子值的和 
}row[maxn],col[maxn];
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(map,-1,sizeof(map));
        memset(col,0,sizeof(col));
        memset(row,0,sizeof(row));
        int cnt=0,row_cnt=0,col_cnt=0;//分别记录白格子的个数,带有行值的黑格子个数,带有列值的黑格子个数 
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                char str[105];
                scanf("%s",str);
                if(str[0] == '.') map[i][j] = ++cnt; //存储白格子的个数并且编号 
                else{
                	map[i][j] = -1;
                	if(str[0] != 'X'){
                    	int tmp = (str[0]-'0')*100 + (str[1]-'0')*10 + str[2]-'0';
                    	col[++col_cnt].x = i;//存储下带有列值黑格子的坐标和这一列白格子的值之和 
                    	col[col_cnt].y = j;
                    	col[col_cnt].z = tmp;
                	}
               		if(str[4] != 'X'){
                    	int tmp = (str[4]-'0')*100 + (str[5]-'0')*10 + str[6]-'0';
                    	row[++row_cnt].x = i;//存储下带有行值黑格子的坐标和这一行白格子的值之和 
                    	row[row_cnt].y = j;
                    	row[row_cnt].z = tmp;
                	}
				}	
            }
        }
        int start=0,end=col_cnt+cnt+row_cnt+2;//起点和终点 
        for(int i = 1;i <= row_cnt;i++){
            int x = row[i].x;
            int y = row[i].y;
            int cnt_len = 0;
            for(int j = y+1;j <= m;j++){ //记录当前行有多少个白色格子 
                if(map[x][j] != -1){ 
                    cnt_len++;
                    addedge(i,row_cnt+map[x][j],8);
                    //白色格子和带有行值的黑格子建边,容量为 8,表示当前这个值的和是由哪几个数字的和得到的。 
                }
                else   break;
            }
            addedge(start,i,row[i].z-cnt_len);//建边 s 到所有带有行值的黑格子,容量为这一行的值之和
			//因为是从 0 到 8 ,所以容量要减去白色格子的数量 
        }
        for(int i = 1;i <= col_cnt;i++){
            int x = col[i].x;
            int y = col[i].y;
            int cnt_len = 0;
            for(int j = x+1;j <= n;j++){//记录当前列有多少个白色格子
                if(map[j][y] != -1){
                    cnt_len++;
                    addedge(row_cnt+map[j][y],row_cnt+cnt+i,8);
                    //白色格子和有列值的黑格子建边,容量为 8,表示当前这个值的和是由哪几个数字的和得到的。 
                }
                else    break;
            }
            addedge(row_cnt+cnt+i,end,col[i].z-cnt_len);//建边所有带有列值的黑格子到t,容量为这一行的值之和
            //因为是从 0 到 8 ,所以权值要减去白色格子的数量
        }
        int ans=dinic(start,end);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(map[i][j]==-1)
                    printf("_");
                else
                    printf("%d",e[v[map[i][j]+row_cnt][1]].flow+1);
                printf("%c",j==m?'\n':' ');
            }
        }
        init(end);
    }
    return 0;

}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值