跳舞链

代码参考
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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值