【题解】NOIP2018 填数游戏

题目戳我

( ext{Solution:})

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

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

考虑几条性质:

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

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

证明:

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

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

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

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

然后对角线是( ext{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
",A[n][m]);
		else {
			if(n==1) {
				printf("%d
",qpow(2,m));
				return;
			}
			int c=m-n;
			if(n<8)R=A[n][n+1];
			printf("%d
",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)并不一定正确,在不会正解的情况下先写暴力打表。

原文地址:https://www.cnblogs.com/h-lka/p/13741597.html