Luogu5512 棋盘问题(2)【加强】

https://www.luogu.com.cn/problem/P5512

搜索

考虑最简单的搜索,数字从小到大开始搜索

首先,搜索第一行第一列,然后逐个\((2,2),(2,3),\cdots,(2,n),(3,2),(3,3),\cdots,(n,n)\)搜索

预处理\(t_{i,j}\)表示\(i,j\)两数是否能够相邻,保证我们填入数字时当前局面一定满足题意

最优性剪枝:当第一行第一列已取的数之和大于等于当前答案,直接停止搜索

取数的过程中,我们通过链表维护,避免多余的循环过程

通过以上简单的搜索,就可以得到\(70\)

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define pr pair<int,int>
#define mp make_pair
#define N 15
#define N1 105
#define N2 205
using namespace std;
int st,n,n1,n2,cnt,tot,kt,prime[N2],sh[N1],a[N][N];
int pre[N1],nxt[N1];
bool pri[N2],t[N1][N1];
pr s[N1];
int dic[2][2]={{0,-1},{-1,0}};
int rans=1000000007,ans[N][N];
void dfs(int cs)
{
    if (cs<n+n)
        st+=a[s[cs-1].first][s[cs-1].second];
    if (st>=rans)
    {
        if (cs<n+n)
            st-=a[s[cs-1].first][s[cs-1].second];
        return;
    }
    if (cs==n1)
    {
        rans=st;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                ans[i][j]=a[i][j];
        return;
    }
    int l=s[cs].first;
    int r=s[cs].second;
    for (int i=nxt[0];i;i=nxt[i])
    {
        bool flag=true;
        for (int j=0;j<2;j++)
        {
            int nl=l+dic[j][0];
            int nr=r+dic[j][1];
            if (nl<1 || nl>n || nr<1 || nr>n || !a[nl][nr])
                continue;
            flag&=t[sh[i]][a[nl][nr]];
        }
        if (flag)
        {
            a[l][r]=sh[i];
            pre[nxt[i]]=pre[i];
            nxt[pre[i]]=nxt[i];
            dfs(cs+1);
            a[l][r]=0;
            pre[nxt[i]]=i;
            nxt[pre[i]]=i;
        }
    }
    if (cs<n+n)
        st-=a[s[cs-1].first][s[cs-1].second];
}
int main()
{
    scanf("%d",&n);
    if (n==1)
    {
        puts("NO");
        return 0;
    }
    n1=n*n;
    n2=n1 << 1;
    pri[1]=true;
    for (int i=2;i<=n2;i++)
    {
        if (!pri[i])
            prime[++cnt]=i;
        for (int j=1;j<=cnt;j++)
        {
            int g=i*prime[j];
            if (g>n2)
                break;
            pri[g]=true;
            if (i % prime[j]==0)
                break;
        }
    }
    for (int i=1;i<=n1;i++)
        for (int j=1;j<=n1;j++)
            if (i!=j && !pri[i+j])
                t[i][j]=true;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (i!=1 || j!=1)
                tot++,sh[tot]=tot+1;
    nxt[0]=1;
    for (int i=1;i<=tot;i++)
        pre[i]=i-1,nxt[i]=i+1;
    nxt[tot]=0;
    for (int i=2;i<=n;i++)
        s[++kt]=mp(1,i);
    for (int i=2;i<=n;i++)
        s[++kt]=mp(i,1);
    for (int i=2;i<=n;i++)
        for (int j=2;j<=n;j++)
            s[++kt]=mp(i,j);
    a[1][1]=1;
    dfs(1);
    if (rans==1000000007)
        puts("NO"); else
        {
            for (int i=1;i<=n;i++)
            {
                for (int j=1;j<=n;j++)
                    printf("%d ",ans[i][j]);
                putchar('\n');
            }
        }
    return 0;
}

还有一个剪枝,我们可以行列交替搜索,避免刚开始搜索时,较小的数字大部分处于数组上部,较大的数字被迫在数组下部搜索,我们知道,较大的数字中质数分布是较少的,所以让小数字与大数字匹配更优,反正我们已经保证了第一行第一列是用较小的数字搜的,其他位置是没有必要控制大小的。行列交替搜索使得大小数字较均匀分布,更易搜出最优解

\(But,\)剩下的\(3\)个点,真的是怎么减都减不下来了

由于这道题是最优解问题,而非任意解问题,因此搜索不得不在已经搜到一个答案后重新回溯,这给我们的搜索带来了很大负担

考虑如何转化成任意解问题,显然,当当前解已经达到了最优解的下限时,我们就可以直接输出了

我们首先可以找到一个最优解的下限\((2n-1)n\),那就是第一行第一列取遍\(1 \sim 2n-1\)的情况,此时已经没有更优解了

实际上,在\(n\)为偶数时,我们可以提高下限

为什么呢?

首先,\(1 \sim 2n-1\)\(n\)个奇数和\(n-1\)个偶数

为了保证相邻的\(x,y\)和为质数,由于\(x+y>2\)\(x+y\)为奇数,所以所有的\((x,y)\)都是一奇一偶的,那么第一行奇偶交错,有\(\frac{n}{2}\)个奇数和\(\frac{n}{2}\)个偶数,同样第一列也奇偶交错,有\(\frac{n}{2}\)个奇数和\(\frac{n}{2}\)个偶数

由于\((1,1)\)位置的数\(1\)被重复计算两次,最终第一行第一列共有\(n-1\)个奇数和\(n\)个偶数,矛盾了

所以下限被提高到\((2n-1)n+1\)

那么我们先搜索最优解的下限,可以当成任意解处理,搜到一个直接退出

由于我们钦定要搜下限,那么如果是奇数,第一行第一列只能在\(1 \le x \le 2n-1\)中取,如果是偶数,第一行第一列只能在\(1 \le x \le 2n\)\(x\ne 2n-1\)中取(因为和为\((2n-1)n+1\)

在搜索前,我们可以预处理数列\(q_{i,j}\)表示能够与\(i,j\)同时相邻的数的个数

如果搜不到最优解的下限,我们就按原来的暴搜再搜一次

这样我们就可以\(AC\)

注:不开\(O2\)还真\(AC\)不了,主要是\(n=9\)搜了很久,其他点在开\(O2\)情况下最大的只跑了\(85ms\),在不开\(O2\)情况下最大的也只跑了\(218ms\)

\(Code:\)

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("-O2")
#pragma GCC optimize("-O3")
#pragma GCC optimize("inline")
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define pr pair<int,int>
#define mp make_pair
#define N 15
#define N1 105
#define N2 205
using namespace std;
int st,n,n0,n1,n2,lim,cnt,tot,kt,prime[N2],sh[N1],q[N1],a[N][N];
int pre[N1],nxt[N1];
int q1[N1][N1];
int q2[N1][N1][N1];
bool pri[N2],t[N1][N1];
pr s[N1];
bool vis[N1];
int dic[2][2]={{0,-1},{-1,0}};
int rans=1000000007,ans[N][N];
void dfs(int cs)
{
    if (cs==n1)
    {
        for (int i=1;i<=n;i++)
        {
            for (int j=1;j<=n;j++)
                printf("%d ",a[i][j]);
            putchar('\n');
        }
        exit(0);
        return;
    }
    int l=s[cs].first;
    int r=s[cs].second;
    if (cs<n0-1)
    {
        int k,o;
        if (l==1)
            k=a[l][r-1]; else
            k=a[l-1][r];
        if (n & 1)
        {
            for (int i=2;i<n0;i++)
                if (!vis[i] && t[i][k])
                {
                    vis[i]=true;
                    a[l][r]=i;
                    dfs(cs+1);
                    vis[i]=false;
                }
        } else
        {
            for (int i=2;i<=n0;i++)
                if (!vis[i] && t[i][k])
                {
                    if (i==n0-1)
                        continue;
                    vis[i]=true;
                    a[l][r]=i;
                    dfs(cs+1);
                    vis[i]=false;
                }
        }
        
    } else
    {
        int k1=a[l][r-1],k2=a[l-1][r];
        for (int i=1;i<=q2[k1][k2][0];i++)
            if (!vis[q2[k1][k2][i]])
            {
                vis[q2[k1][k2][i]]=true;
                a[l][r]=q2[k1][k2][i];
                dfs(cs+1);
                vis[q2[k1][k2][i]]=false;
            }
    }
}
void dfs2(int cs)
{
    if (cs<n+n)
        st+=a[s[cs-1].first][s[cs-1].second];
    if (st>=rans)
    {
        if (cs<n+n)
            st-=a[s[cs-1].first][s[cs-1].second];
        return;
    }
    if (cs==n1)
    {
        rans=st;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                ans[i][j]=a[i][j];
        return;
    }
    int l=s[cs].first;
    int r=s[cs].second;
    for (int i=nxt[0];i;i=nxt[i])
    {
        bool flag=true;
        for (int j=0;j<2;j++)
        {
            int nl=l+dic[j][0];
            int nr=r+dic[j][1];
            if (nl<1 || nl>n || nr<1 || nr>n || !a[nl][nr])
                continue;
            flag&=t[sh[i]][a[nl][nr]];
        }
        if (flag)
        {
            a[l][r]=sh[i];
            pre[nxt[i]]=pre[i];
            nxt[pre[i]]=nxt[i];
            dfs2(cs+1);
            a[l][r]=0;
            pre[nxt[i]]=i;
            nxt[pre[i]]=i;
        }
    }
    if (cs<n+n)
        st-=a[s[cs-1].first][s[cs-1].second];
}
int main()
{
    scanf("%d",&n);
    if (n==1)
    {
        puts("NO");
        return 0;
    }
    n0=n+n;
    n1=n*n;
    n2=n1 << 1;
    pri[1]=true;
    for (int i=2;i<=n2;i++)
    {
        if (!pri[i])
            prime[++cnt]=i;
        for (int j=1;j<=cnt;j++)
        {
            int g=i*prime[j];
            if (g>n2)
                break;
            pri[g]=true;
            if (i % prime[j]==0)
                break;
        }
    }
    for (int i=1;i<=n1;i++)
        for (int j=1;j<=n1;j++)
            if (i!=j && !pri[i+j])
                q[i]++,q1[i][q[i]]=j,t[i][j]=true;
    for (int i=1;i<=n1;i++)
        for (int j=1;j<=n1;j++)
            if (i!=j)
                for (int k=1;k<=n1;k++)
                    if (k!=i && k!=j)
                        if (!pri[i+k] && !pri[j+k])
                            q2[i][j][++q2[i][j][0]]=k;
    for (int i=2;i<=n;i++)
        s[++kt]=mp(1,i);
    for (int i=2;i<=n;i++)
        s[++kt]=mp(i,1);
    for (int i=2;i<=n;i++)
    {
        for (int j=1;j<=n-i+1;j++)
            s[++kt]=mp(i,i+j-1);
        for (int j=2;j<=n-i+1;j++)
            s[++kt]=mp(i+j-1,i);
    }
    a[1][1]=1;
    vis[1]=true;
    lim=n*(n0-1)+1;
    dfs(1);
    memset(a,0,sizeof(a));
    a[1][1]=1;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            if (i!=1 || j!=1)
                tot++,sh[tot]=tot+1;
    nxt[0]=1;
    for (int i=1;i<=tot;i++)
        pre[i]=i-1,nxt[i]=i+1;
    nxt[tot]=0;
    dfs2(1);
    if (rans==1000000007)
        puts("NO"); else
        {
            for (int i=1;i<=n;i++)
            {
                for (int j=1;j<=n;j++)
                    printf("%d ",ans[i][j]);
                putchar('\n');
            }
        }
    return 0;
}
posted @ 2020-09-11 20:05  GK0328  阅读(115)  评论(0编辑  收藏  举报