代码参考
https://www.luogu.org/problemnew/solution/P4929
知识点参考
https://www.cnblogs.com/grenet/p/3145800.html
才知道列的数量要精准计算出来,不如错误
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <map>
#include <queue>
#include <stack>
#include <set>
#define max(a,b) a>b?a:b
#define min(a,b) a>b?b:a
#define ll long long
#define maxn 10010
#define mod 1000000007
#define INF 0x3f3f3f
using namespace std;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0',ch=getchar();}
return s*w;
}
int n,m,cnt;
int u[maxn],d[maxn],l[maxn],r[maxn]; //创造上下左右四个方向
int h[maxn],num[maxn],col[maxn],row[maxn],Ans[maxn];//h为行的头节点,row为行,col为列,ans储存答案,num储存列为1的数量
void init() //初始化
{
for(int i=0;i<=m;i++)
{
l[i]=i-1,r[i]=i+1;
u[i]=d[i]=i;
}
l[0]=m,r[m]=0; //l[0]左边是m,r[m]右边是0
memset(h,-1,sizeof(h));
cnt=m+1; //开始时有m个结点(0结点和各列头结点)
}
void add(int x,int y) //将为1的点与四周的点建立关系,x为行,y为列
{
row[cnt]=x; //这个点的行
col[cnt]=y; //这个点的列
//点的上个节点为y,下个节点为y的下个节点
u[cnt]=y;
d[cnt]=d[y];
u[d[y]]=cnt;
d[y]=cnt;
if(h[x]==-1)h[x]=l[cnt]=r[cnt]=cnt; //该行没有别的点,把第一个加入的点作为该行的行头结点
else
{
**//注意行的头节点为h[x]**
l[cnt]=l[h[x]];
r[cnt]=h[x];
r[l[h[x]]]=cnt;
l[h[x]]=cnt;
}
cnt++;
num[y]++; //增加一个点,则该列的点+1
}
void del(int y) //y为列,删除y列和与y列上有点的行,因为选择了y列,一次选择中,只能选取y列的其中一行,则与y列上相关的点不可能再次被选择,可以不用考虑
{
r[l[y]]=r[y],l[r[y]]=l[y]; //删除列,但实际上y的右边r[y],y的左边l[y]还保留着
for(int i=d[y];i!=y;i=d[i]) //对列做行循环,从y的下一个开始,到y结束
for(int j=r[i];j!=i;j=r[j]) //对每一行做列循环,从i的右边开始,到i结束
{
u[d[j]]=u[j];
d[u[j]]=d[j];
num[col[j]]--;
}
}
void resume(int y) //回溯复原删除的关系
{
for(int i=d[y];i!=y;i=d[i]) //经测试发现,从下回溯到上和从上回溯到下答案一样,时间也差不了多少
for(int j=r[i];j!=i;j=r[j]) //经测试发现,从左回溯到右和从右回溯到左答案一样,时间也差不了多少
{
u[d[j]]=j;
d[u[j]]=j;
num[col[j]]++;
}
r[l[y]]=y; //恢复列的关系
l[r[y]]=y;
}
bool dance(int deep) //核心函数,deep表示选择的行数
{
if(r[0]==0) //当r【0】==0时,及表示全部的列都被覆盖,因为列都被删除没了
{
for(int i=0;i<deep;i++)printf("%d ",Ans[i]);
return 1;
}
int y=r[0];
for(int i=r[0];i!=0;i=r[i])if(num[i]<num[y])y=i; //找到列上点最少的点,这样的列由于可供选择的行比较少,在失败的时候,回溯的时间短
del(y);
for(int i=d[y];i!=y;i=d[i]) //选择删除哪一行
{
Ans[deep]=row[i];
for(int j=r[i];j!=i;j=r[j])del(col[j]);
if(dance(deep+1))return 1;
for(int j=l[i];j!=i;j=l[j])resume(col[j]); //经测试发现,这个回溯恢复要与删除顺序相反,时间差了8倍
}
resume(y); 要记得恢复
return 0;
}
int main()
{
n=read(),m=read();
init();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(read())add(i,j);
if(!dance(0))printf("No Solution!");
}
https://www.luogu.org/problem/P1074
靶形数独
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <map>
#include <queue>
#include <stack>
#include <set>
#define max(a,b) a>b?a:b
#define min(a,b) a>b?b:a
#define ll long long
#define maxn 200010
#define mod 1000000007
#define INF 0x3f3f3f
#define n 9
using namespace std;
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=s*10+ch-'0',ch=getchar();}
return s*w;
}
int score[90]={0,
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 u[maxn],d[maxn],l[maxn],r[maxn],v[maxn];
int h[maxn],row[maxn],col[maxn],Ans[maxn],num[maxn],cnt,ans=-1;
void place(int &r,int &c1,int &c2,int &c3,int &c4,int i,int j,int k) //将对应的i,j,k,进行转化,这样就可以知道答案是哪行哪列
{
r=((i-1)*n+j-1)*n+k; //为什么乘以n呢,因为k的最大为9,(j-1)*n+k最大为90,使得每个i,j,k独立即可
c1=(i-1)*n+j; //从1开始 每个都要精准计算出列
c2=n*n+(i-1)*n+k;
c3=2*n*n+(j-1)*n+k;
c4=3*n*n+((i-1)/3+(j-1)/3*3)*n+k;
}
void init()
{
int x=n*n*n+n,y=n*n*4;
for(int i=0;i<=y;i++)
{
l[i]=i-1,r[i]=i+1;
u[i]=d[i]=i;
}
l[0]=y,r[y]=0;
memset(h,-1,sizeof(h));
cnt=y+1;
}
void add(int x,int y)
{
row[cnt]=x,col[cnt]=y;
u[cnt]=y,d[cnt]=d[y],u[d[y]]=cnt,d[y]=cnt;
if(h[x]==-1)h[x]=l[cnt]=r[cnt]=cnt;
else
{
r[cnt]=h[x],l[cnt]=l[h[x]];
r[l[h[x]]]=cnt,l[h[x]]=cnt;
}
cnt++;
num[y]++;
}
void del(int y)
{
r[l[y]]=r[y],l[r[y]]=l[y];
for(int i=d[y];i!=y;i=d[i])
for(int j=r[i];j!=i;j=r[j])
{
u[d[j]]=u[j];
d[u[j]]=d[j];
num[col[j]]--;
}
}
void resume(int y)
{
for(int i=d[y];i!=y;i=d[i])
for(int j=r[i];j!=i;j=r[j])
{
u[d[j]]=j;
d[u[j]]=j;
num[col[j]]++;
}
r[l[y]]=y,l[r[y]]=y;
}
void dance(int deep)
{
if(!r[0])
{
int sum=0;
for(int i=0;i<deep;i++)
sum+=score[(Ans[i]-1)/n+1]*((Ans[i]-1)%n+1); //Ans[i]储存的是行的值((i-1)*n+(j-1))*n+k,根据score的(i-1)*n+j Ans[i]-1是因为当k为9时,直接除以n会使得n+1,所以先减1,后面那个取模也是,不先减1,会使得k=9变成k=0
ans=max(ans,sum);
return ;
}
int y=r[0];
for(int i=r[0];i!=0;i=r[i])if(num[i]<num[y])y=i;
del(y);
for(int i=d[y];i!=y;i=d[i])
{
Ans[deep]=row[i];
for(int j=r[i];j!=i;j=r[j])del(col[j]);
dance(deep+1);
for(int j=l[i];j!=i;j=l[j])resume(col[j]);
}
resume(y);
}
int main()
{
init();
int r,c1,c2,c3,c4;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
{
int k=read();
if(k)
{
place(r,c1,c2,c3,c4,i,j,k);
add(r,c1),add(r,c2),add(r,c3),add(r,c4);
v[c1]=v[c2]=v[c3]=v[c4]=1;
}
}
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
for(int k=1;k<=9;k++)
{
place(r,c1,c2,c3,c4,i,j,k);
if(v[c1]||v[c2]||v[c3]||v[c4])continue; //这样c1 c2 c3 c4 列只有一个选择,必须选他们
add(r,c1),add(r,c2),add(r,c3),add(r,c4);
}
dance(0);
printf("%d",ans);
}