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;
}