1. 程式人生 > >wikioi p1174 靶形數獨

wikioi p1174 靶形數獨

這題我原先是下標程的,先貼標程程式碼,待會我再貼我自己的程式碼。

pascal 不喜勿噴~

type ctype=array[1..9,1..9,1..9]of boolean;

     nctype=array[1..9,1..9]of longint;

const w:array[1..9,1..9]of integer=((6,6,6,6,6 ,6,6,6,6),

                                         (6,7,7,7,7 ,7,7,7,6),

                                         (6,7,8,8,8 ,8,8,7,6),

                                         (6,7,8,9,9 ,9,8,7,6),

                                         (6,7,8,9,10,9,8,7,6),

                                         (6,7,8,9,9 ,9,8,7,6),

                                         (6,7,8,8,8 ,8,8,7,6),

                                         (6,7,7,7,7 ,7,7,7,6),

                                         (6,6,6,6,6 ,6,6,6,6));

      vb:array[1..9,1..9]of integer=((1,1,1,2,2,2,3,3,3),

                                       (1,1,1,2,2,2,3,3,3),

                                       (1,1,1,2,2,2,3,3,3),

                                       (4,4,4,5,5,5,6,6,6),

                                       (4,4,4,5,5,5,6,6,6),

                                       (4,4,4,5,5,5,6,6,6),

                                       (7,7,7,8,8,8,9,9,9),

                                       (7,7,7,8,8,8,9,9,9),

                                       (7,7,7,8,8,8,9,9,9));

var v:array[1..82]of boolean;

    k:array[0..82,1..2]of longint;

    c:ctype;nc:nctype;

    depth,ans:longint;

    flag:boolean;

procedure init;

var i,j,x,m,n:longint;

begin

depth:=0;ans:=0;flag:=false;

for i:=1 to 9 do for j:=1 to 9 do nc[i,j]:=9;

fillchar(c,sizeof(c),true);fillchar(v,sizeof(v),false);

for i:=1 to 9 do

begin

   for j:=1 to 9 do

    begin

      read(x);

     if x>0 then begin

                    ans:=ans+x*w[i,j];

                    nc[i,j]:=0;

                    for m:=1 to 9 do

                     for n:=1 to 9 do

                      begin

                       if nc[m,n]=0 then continue;

                       if (c[m,n,x])and((m=i)or(n=j)or(vb[i,j]=vb[m,n]))

                        then begin c[m,n,x]:=false;dec(nc[m,n]);end;

                      end;

                   end

              else begin inc(depth);k[depth,1]:=i;k[depth,2]:=j;end;

    end;

   readln;

end;

end;


procedure outit;

begin

if flag then writeln(ans) else writeln('-1');

end;


procedure dfs(d,max:longint;c:ctype;nc:nctype);

var i,j,min,m,x,y:longint;c1:ctype;nc1:nctype;

begin

if d=depth+1 then begin if max>ans then ans:=max;flag:=true;exit; end;


min:=10;

for i:=1 to depth do if not (v[i])and(nc[k[i,1],k[i,2]]<min) then begin min:=nc[k[i,1],k[i,2]];m:=i;end;


x:=k[m,1];y:=k[m,2];v[m]:=true;

for i:=1 to 9 do if c[x,y,i] then

   begin

     c1:=c;nc1:=nc;

     for j:=1 to depth do

      begin

       if (not v[j])and c1[k[j,1],k[j,2],i]and((k[j,1]=x)or(k[j,2]=y)or(vb[k[j,1],k[j,2]]=vb[x,y]))

        then begin c1[k[j,1],k[j,2],i]:=false;dec(nc1[k[j,1],k[j,2]]);end;

       if nc1[k[j,1],k[j,2]]=0 then break;

      end;

     dfs(d+1,max+i*w[x,y],c1,nc1);

   end;

v[m]:=false;

end;


begin                                 {(input,'d:in.txt');(input);(,'d:out.txt');(output);}

init;
dfs(1,ans,c,nc);

outit;                                {close(input);close(output);}

end.          


 然後我們就可以思考一個問題。數獨的規則。

每行 每列 每個小數獨都不能有相同的數字。

所以我們分別用Line list和sud記錄重複。

這樣搜尋效率會增加。

但是bfs存在一個問題。就是隻能得40分。

因為會超空間。

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<queue>
#include<iostream>
#include<memory.h>
using namespace std;
const int MAX_N = 9;
const int MAX_M = 10;
int score[MAX_N][MAX_N]={
	          {6,6,6,6,6,6,6,6,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,9,10,9,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,6,6,6,6,6,6,6,6},
			  }; //靶型得分 
int ans = -1; //表示答案 
struct node
{
	int sudoku[MAX_M][MAX_N];
	bool line[MAX_M][MAX_M]; //行 
	bool list[MAX_M][MAX_M]; //列
	bool sud[MAX_M][MAX_M];  //數獨 
	int x,y;
}head,next;  //靶型數獨 
queue<struct node> Q;
int solve_score(struct node R) //計算總得分 
{
	int i,j;
	int tmp = 0;
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	tmp+=score[i][j]*R.sudoku[i][j];
	return tmp ;
}
int which(int x,int y) //尋找該單元格在第幾個小數獨中 
{
	if (x>=0&&x<=2)
	{
		if (y>=0&&y<=2) return 1;
		if (y>=3&&y<=5) return 2;
		if (y>=6&&y<=8) return 3;
	}
	if (x>=3&&x<=5)
	{
		if (y>=0&&y<=2) return 4;
		if (y>=3&&y<=5) return 5;
		if (y>=6&&y<=8) return 6;
	}
	if (x>=6&&x<=8)
	{
		if (y>=0&&y<=2) return 7;
		if (y>=3&&y<=5) return 8;
		if (y>=6&&y<=8) return 9;
	}
}
int putG(struct node R)
{
	int i,j;
	for (i=0;i<MAX_N;i++)
	{
		for (j=0;j<MAX_N;j++)
		printf("%d ",R.sudoku[i][j]);
		printf("\n");
	}
	printf("\n");
}
int init() //初始化 
{
	memset(head.line,false,sizeof(head.line));
	memset(head.list,false,sizeof(head.list));
	memset(head.sud,false,sizeof(head.sud));
	int i ,j ;
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	scanf("%d",&head.sudoku[i][j]);
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	if (head.sudoku[i][j])
	{
		head.line[i][head.sudoku[i][j]]=true;
		head.list[j][head.sudoku[i][j]]=true;
		head.sud[which(i,j)][head.sudoku[i][j]]=true;
	}
	head.x = head.y=0;
	Q.push(head);
}
bool Cash(struct node R,int x,int y) //檢查是否發生衝突 
{
	int i=R.sudoku[x][y];
	if (R.line[x][i]) return false;
	if (R.list[y][i]) return false;
	if (R.sud[which(x,y)][i])  return false;
	return true;
}
int work()
{
	int i;
	int x,y;
	int nx,ny;
	while(!Q.empty())
	{
		head=Q.front();
		Q.pop();
		x=head.x;
		y=head.y;
		//putG(head);
		//for (int v=1;v<=50000000;v++);
		if (x==MAX_N)
		{
			ans=max(solve_score(head),ans);
			continue;
		}
		if (y==MAX_N-1) nx=x+1,ny=0;
		else nx=x,ny=y+1;
		if (head.sudoku[x][y])
		{
			next=head;
			next.x=nx;
			next.y=ny;
			Q.push(next);
		}
		else
		for (i=1;i<=MAX_N;i++)
		{
			next=head;
			next.x=nx;
			next.y=ny;
			next.sudoku[x][y]=i;
			if (Cash(next,x,y))
			{
				next.line[x][i] = true;
				next.list[y][i] = true;
				next.sud[which(x,y)][i] =true;
				Q.push(next);
			}
		}
	}
}
int put()
{
	printf("%d",ans);
} 
int main()
{
	init();
	work();
	//putG(head);
	put();
	//while(1);
	return 0;
} 

然後我們可以將他改成dfs

這樣就不會超空間了。

不過任然存在一個問題,就是超時。

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<memory.h>
using namespace std;
const int MAX_N = 9;
const int MAX_M = 10;
int score[MAX_N][MAX_N]={
	          {6,6,6,6,6,6,6,6,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,9,10,9,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,6,6,6,6,6,6,6,6},
			  }; //靶型得分 
int ans = -1; //表示答案  
int sudoku[MAX_M][MAX_N];//靶型數獨 
bool line[MAX_M][MAX_M]; //行 
bool list[MAX_M][MAX_M]; //列
bool sud[MAX_M][MAX_M];  //數獨
int which(int x,int y) //尋找該單元格在第幾個小數獨中 
{
	if (x>=0&&x<=2)
	{
		if (y>=0&&y<=2) return 1;
		if (y>=3&&y<=5) return 2;
		if (y>=6&&y<=8) return 3;
	}
	if (x>=3&&x<=5)
	{
		if (y>=0&&y<=2) return 4;
		if (y>=3&&y<=5) return 5;
		if (y>=6&&y<=8) return 6;
	}
	if (x>=6&&x<=8)
	{
		if (y>=0&&y<=2) return 7;
		if (y>=3&&y<=5) return 8;
		if (y>=6&&y<=8) return 9;
	}
}
int init() //初始化 
{
	memset(line,false,sizeof(line));
	memset(list,false,sizeof(list));
	memset(sud,false,sizeof(sud));
	int i ,j ;
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	scanf("%d",&sudoku[i][j]);
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	if (sudoku[i][j])
	{
		line[i][sudoku[i][j]]=true;
		list[j][sudoku[i][j]]=true;
		sud[which(i,j)][sudoku[i][j]]=true;
	}
}
bool Cash(int x,int y,int i) //檢查是否發生衝突 
{
	if (line[x][i]) return false;
	if (list[y][i]) return false;
	if (sud[which(x,y)][i])  return false;
	return true;
}
int dfs(int x,int y,int tmp)
{
	if (x==MAX_N)
	{
		ans=max(tmp,ans);
		return 0;
	}
	int i;
	int nx,ny;
	if (y==MAX_N-1) nx=x+1,ny=0;
	else nx=x,ny=y+1;
	if (sudoku[x][y])
	dfs(nx,ny,tmp+sudoku[x][y]*score[x][y]);
	else
	for (i=1;i<=MAX_N;i++)
	if (Cash(x,y,i))
	{
		sudoku[x][y]=i;
		line[x][i] = true;
		list[y][i] = true;
		sud[which(x,y)][i] = true;
		dfs(nx,ny,tmp+i*score[x][y]);
		line[x][i] = false;
		list[y][i] = false;
		sud[which(x,y)][i] = false;
        sudoku[x][y]=0;//這句話必不可少
	}
}
int put()
{
	printf("%d",ans);
} 
int main()
{
	init();
	dfs(0,0,0);
	put();
	return 0;
} 

根據啟發式搜尋的思想,運用估價函式可以通過改變搜尋的順序增加速度。

但是這題資料比較奇葩,如果倒過來搜,也就是從最後一列最後一行開始搜尋的話速度會快很多。

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
#include<iostream>
#include<memory.h>
using namespace std;
const int MAX_N = 9;
const int MAX_M = 10;
int score[MAX_N][MAX_N]={
	          {6,6,6,6,6,6,6,6,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,9,10,9,8,7,6},
	          {6,7,8,9,9,9,8,7,6},
	          {6,7,8,8,8,8,8,7,6},
	          {6,7,7,7,7,7,7,7,6},
	          {6,6,6,6,6,6,6,6,6},
			  }; //靶型得分 
int ans = -1; //表示答案  
int sudoku[MAX_M][MAX_N];//靶型數獨 
bool line[MAX_M][MAX_M]; //行 
bool list[MAX_M][MAX_M]; //列
bool sud[MAX_M][MAX_M];  //數獨
int which(int x,int y) //尋找該單元格在第幾個小數獨中 
{
	if (x>=0&&x<=2)
	{
		if (y>=0&&y<=2) return 1;
		if (y>=3&&y<=5) return 2;
		if (y>=6&&y<=8) return 3;
	}
	if (x>=3&&x<=5)
	{
		if (y>=0&&y<=2) return 4;
		if (y>=3&&y<=5) return 5;
		if (y>=6&&y<=8) return 6;
	}
	if (x>=6&&x<=8)
	{
		if (y>=0&&y<=2) return 7;
		if (y>=3&&y<=5) return 8;
		if (y>=6&&y<=8) return 9;
	}
}
int init() //初始化 
{
	memset(line,false,sizeof(line));
	memset(list,false,sizeof(list));
	memset(sud,false,sizeof(sud));
	int i ,j ;
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	scanf("%d",&sudoku[i][j]);
	for (i=0;i<MAX_N;i++)
	for (j=0;j<MAX_N;j++)
	if (sudoku[i][j])
	{
		line[i][sudoku[i][j]]=true;
		list[j][sudoku[i][j]]=true;
		sud[which(i,j)][sudoku[i][j]]=true;
	}
}
bool Cash(int x,int y,int i) //檢查是否發生衝突 
{
	if (line[x][i]) return false;
	if (list[y][i]) return false;
	if (sud[which(x,y)][i])  return false;
	return true;
}
int dfs(int x,int y,int tmp)
{
	if (x==MAX_N)
	{
		ans=max(tmp,ans);
		return 0;
	}
	int i;
	int nx,ny;
	if (y==MAX_N-1) nx=x+1,ny=0;
	else nx=x,ny=y+1;
	if (sudoku[x][y])
	dfs(nx,ny,tmp+sudoku[x][y]*score[x][y]);
	else
	for (i=1;i<=MAX_N;i++)
	if (Cash(x,y,i))
	{
		sudoku[x][y]=i;
		line[x][i] = true;
		list[y][i] = true;
		sud[which(x,y)][i] = true;
		dfs(nx,ny,tmp+i*score[x][y]);
		line[x][i] = false;
		list[y][i] = false;
		sud[which(x,y)][i] = false;
                sudoku[x][y]=0;//這句話必不可少
	}
}
int put()
{
	printf("%d",ans);
} 
int main()
{
	init();
	dfs(0,0,0);
	put();
	return 0;
} 

另外,網上說dl的速度非常快。但是不會寫啊。

啟發式搜尋其實還是不難的。

但是估價函式的設計還比較麻煩。