洛谷 P1549 [NOIP1997 提高组] 棋盘问题

· · 题解

Problem

构造一个n\times n(n\le 10)的矩阵,在矩阵中填入1,2,\dots,n^2,使得任意相邻的数之和为素数。

约定:左上角的格子里必须填数字1

不存在解则输出NO,存在的话输出所有解中,第一行和第一列之和最小的排列方案。

Solution

首先n\le10,可以暴搜。

剪枝1

预处理出200以内所有的质数。

剪枝2

为了保证第一行和第一列之和最小,我们可以先填第一行和第一列,再填行列均在[2,n]中的格子并优先选择大的数填放。那么我们得到的第一个合法的方案后,可以记录此时的第一行和第一列的和并对之后填第一行第一列的和进行最优化剪枝

这样就有60pts了。

剪枝3

预处理出i+j是不是质数。

大概就是一点常数优化,但是这让check函数比较好写。

剪枝4

每填入一个格子,就将其与其四周的已经填了的格子进行判断其和是否为质数。而不是等到构造完毕了再整体判断。

这样进行下一步判断时,前面的方案一定是合法的。并且最后也不需要再进行判断了。

剪枝5

假设当前的第一行+第一列为sum。

如果以当前的第一行第一列搜索到了一组正解,那么即使更新最小的第一行+第一列,并直接退出对右下角的格子的搜索。因为即使又搜出来的几组解,这些解的sum和当前最优解的sum没有区别。

这个时候应该及时的去调整第一行和第一列

到这里,您就已经可以A了这道题了。

如果您对于您的剪枝十分自信,不妨去看一看加强版:P5512

Code

/**************************************************************
 * Problem: 1549
 * Author: Vanilla_chan
 * Date: 20210315
**************************************************************/
//预编译等部分已去除
#define M 210
#define N 20
int n;
bool book[M];
int prime[M][M];
void init()
{
    book[1]=1;
    for(int i=2;i<=200;i++)
    {
        if(!book[i])
        {
            for(int j=2;i*j<=200;j++)
            {
                book[i*j]=1;
            }
        }
    }
    for(int i=1;i<=n*n;i++)
        for(int j=1;j<=n*n;j++)
            prime[i][j]=book[i+j];
}
int ans;
int ANS[N][N];
int now[N][N];
bool vis[M];
int dt[4][2]={0,1,0,-1,1,0,-1,0};
IL bool exist(int x)
{
    return x>=1&&x<=n;
}
bool check(int x,int y)
{
    for(int t=0;t<4;t++)
    {
        if(exist(x+dt[t][0])&&exist(y+dt[t][1]))
        {
            if(now[x+dt[t][0]][y+dt[t][1]]!=0)
            {
                if(prime[now[x][y]][now[x+dt[t][0]][y+dt[t][1]]])
                {
                    return 0;
                }
            }
        }
    }
    return 1;
}
void dfs3(int x,int y,int sum)
{
    if(sum>=ans) return;
    if(y==n+1) y=2,x++;
    if(x==n+1)
    {
        ans=sum;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                ANS[i][j]=now[i][j];
            }
        }
        return;
    }
    for(int i=n*n;i>=1;i--)
    {
        if(vis[i]) continue;
        vis[i]=1;
        now[x][y]=i;
        if(check(x,y)) dfs3(x,y+1,sum);
        vis[i]=0;
        now[x][y]=0;
    }
}
void dfs2(int y,int sum)
{
    if(sum>=ans) return;
    if(y==n+1)
    {
        dfs3(2,2,sum);
        return;
    }
    for(int i=1;i<=n*n;i++)
    {
        if(vis[i]) continue;
        vis[i]=1;
        now[1][y]=i;
        if(check(1,y)) dfs2(y+1,sum+i);
        now[1][y]=0;
        vis[i]=0;
    }
}
void dfs1(int x,int sum)
{
    if(sum>=ans) return;
    if(x==n+1)
    {
        dfs2(2,sum);
        return;
    }
    for(int i=1;i<=n*n;i++)
    {
        if(vis[i]) continue;
        vis[i]=1;
        now[x][1]=i;
        if(check(x,1)) dfs1(x+1,sum+i);
        vis[i]=0;
        now[x][1]=0;
    }
}
int main()
{
    n=read();
    init();
    now[1][1]=1;
    vis[1]=1;
    ans=100000;
    dfs1(2,0);
    if(ans==100000) cout<<"NO"<<endl;
    else
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                cout<<ANS[i][j]<<" ";
            }
            cout<<endl;
        }
    }
    return 0;
}