【题解】NOIP2018 填数游戏

题目戳我

\(\text{Solution:}\)

题目标签是\(dp,\)但是纯暴力打表找规律可以有\(65\)分。

首先是对于\(O(2^{nm}*nm)\)的暴力搜索,显然都会。

考虑几条性质:

  • 每一条由左下到右上的对角线需要非严格单调递减。

  • \(a[i][j-1]=a[i-1][j]\)则以\(a[i][j]\)为左上角的矩阵填的数必须相等。

证明:

对于第一条,若不满足这条性质,则必然存在一个路径,\(R\to 1,D\to 0\)使得其不满足题意。

对于第二条,首先满足\(R\to x,D\to x,\)则对于后面的所有路径,若有一个不相等的则必然存在一条路径\(D\to Big\)\(R\to Small\)使得题目不成立。

于是,设\(a[i][j]\)表示第\(j\)\(i\to n\)行数字的状压结果,\(b[i][j]\)表示以\((i,j)\)为左上角的矩阵的数字是不是相等。

考虑每次填数完毕就判断一次。若矩阵相等,则\(\text{b[i][j]=b[i][j+1]&&(a[i][j+1]>>1)==a[i+1][j]}\)

然后对角线是\(\text{x<n&&y>1&&g[x][y]==g[x+1][y-1]&&!b[x+1][y](False)}\)

代码中保证只有上一次填了\(1\)下一次才能继续填。否则必须填\(0\).从而起到了剪枝的效果。

于是可以打表,继续找规律:

int A[9][9]= {
	{0,0,0,0,0,0,0,0,0},
	{0,2,4,8,16,32,64,128,256},
	{0,0,12,36,108,324,972,2916,8748},
	{0,0,0,112,336,1008,3024,9072,27216},
	{0,0,0,0,912,2688,8064,24192,72576},
	{0,0,0,0,0,7136,21312,63936,191808},
	{0,0,0,0,0,0,56768,170112,510336},
	{0,0,0,0,0,0,0,453504,1360128},
	{0,0,0,0,0,0,0,0,3626752},
};

观察一下,上半个矩阵中,有很多值满足\(a[i][j]=a[i][j-1]*3.\)

所以,特判掉\(n=m\)的情况后,直接调用\(A(n,n+1)\)并使用快速幂即可。

#include<bits/stdc++.h>
using namespace std;
int n,m,R=10879488;
int A[9][9]= {
	{0,0,0,0,0,0,0,0,0},
	{0,2,4,8,16,32,64,128,256},
	{0,0,12,36,108,324,972,2916,8748},
	{0,0,0,112,336,1008,3024,9072,27216},
	{0,0,0,0,912,2688,8064,24192,72576},
	{0,0,0,0,0,7136,21312,63936,191808},
	{0,0,0,0,0,0,56768,170112,510336},
	{0,0,0,0,0,0,0,453504,1360128},
	{0,0,0,0,0,0,0,0,3626752},
};
const int mod=1e9+7;
inline int add(int x,int y) {
	return (x+y)%mod;
}
inline int mul(int x,int y) {
	return 1ll*x*y%mod;
}
namespace P3 {
	inline int qpow(int x,int y) {
		int res=1;
		while(y) {
			if(y&1)res=mul(res,x);
			x=mul(x,x);
			y>>=1;
		}
		return res;
	}
	void solve() {
		if(n>m)swap(n,m);
		if(n==m)printf("%d\n",A[n][m]);
		else {
			if(n==1) {
				printf("%d\n",qpow(2,m));
				return;
			}
			int c=m-n;
			if(n<8)R=A[n][n+1];
			printf("%d\n",mul(R,qpow(3,c-1)));
		}
	}
}
namespace Biao {
	int a[13][13],g[30][30],ans=0;
	bool b[100][100];
	bool check(int x,int y) {
		a[x][y]=a[x+1][y]|(g[x][y]<<(n-x));
		if(y==m)b[x][y]=1;
		else b[x][y]=b[x][y+1]&&(a[x][y+1]>>1)==a[x+1][y];
		if(x<n&&y>1&&g[x][y]==g[x+1][y-1]&&!b[x+1][y])return false;
		return true;
	}
	void dfs(int x,int y) {
		if(y<1) {
			dfs(x-1,m);
			return;
		}
		if(x<1) {
			ans++;
			return;
		}
		if(x==n||y==1||g[x+1][y-1]==1) {
			g[x][y]=1;
			if(check(x,y))dfs(x,y-1);
		}
		g[x][y]=0;
		if(check(x,y))dfs(x,y-1);
	}
	void solve() {
		ans=0;
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(g,0,sizeof(g));
		if(n>m)swap(n,m);
		dfs(n,m);R=ans;
	}
}
int main() {
	scanf("%d%d",&n,&m);
	P3::solve();
	return 0;
}

参考:https://www.luogu.com.cn/blog/2003ok/solution-p5023 Lisy_03 的博客

(侵删)

总结:数据范围小所想到的状压\(dp\)并不一定正确,在不会正解的情况下先写暴力打表。

posted @ 2020-09-27 20:19  Refined_heart  阅读(258)  评论(0编辑  收藏  举报