【题解】LuoGu5023:填数游戏

原题传送门

这道题有一个非常实用的考场策略在里面,就是暴力
当年考场里写了最慢的暴力,找出了1~4的规律,拿到了75分

const
    p = 1000000007;
var
    a : array[0..1000, 0..1000] of char;
    flag : boolean;
    s, num : array[0..1000000] of ansistring;
    n, m, tot : longint;
    ans : int64;
    sum : ansistring;

function max(x, y : longint) : longint;

begin
    if x > y then exit(x) else exit(y);
end;

procedure check;
var
    i, j, x, y : longint;

begin
    flag := true;
    for i := 1 to tot do
    begin
        x := 1; y := 1;
        num[i] := '';
        for j := 1 to length(s[i]) do
        begin
            if s[i][j] = 'R' then inc(y) else inc(x);
            num[i] := num[i] + a[x][y];
        end;
        for j := 1 to i - 1 do
            if num[i] < num[j] then
            begin
                flag := false;
                break;
            end;
        if not flag then break;
    end;
    if flag then ans := (ans + 1) mod p;
end;

procedure work(i, j : longint);

begin
    if (i = n) and (j = m) then
    begin
        inc(tot);
        s[tot] := sum;
        exit;
    end;
    if j < m then
    begin
        sum := sum + 'R';
        work(i, j + 1);
        delete(sum, length(sum), 1);
    end;
    if i < n then
    begin
        sum := sum + 'D';
        work(i + 1, j);
        delete(sum, length(sum), 1);
    end;
end;

procedure dfs(i, j : longint);

begin
    if i > n then
    begin
        check; exit;
    end;
    if j < m then
    begin
        a[i][j] := '0';
        dfs(i, j + 1);
        a[i][j] := '1';
        dfs(i, j + 1);
    end else
    begin
        a[i][j] := '0';
        dfs(i + 1, 1);
        a[i][j] := '1';
        dfs(i + 1, 1);
    end;
end;

procedure do4;
var
    i, j : longint;

begin
    if n = 4 then j := m else j := n;
    if j = 4 then
        writeln(912) else
    begin
        ans := 2688;
        for i := 6 to j do ans := ans * 3 mod p;
        writeln(ans);
    end;
end;

procedure do3;
var
    i, j : longint;

begin
    if n = 3 then j := m else j := n;
    if j = 1 then
        writeln(8) else
    if j = 2 then
        writeln(36) else
    begin
        ans := 112;
        for i := 4 to j do ans := ans * 3 mod p;
        writeln(ans);
    end;
end;

procedure do2;
var
    i, j : longint;

begin
    if n = 2 then j := m else j := n;
    ans := 4;
    for i := 2 to j do ans := ans * 3 mod p;
    writeln(ans);
end;

procedure do1;
var
    i : longint;

begin
    ans := 1;
    for i := 1 to max(n, m) do ans := ans * 2 mod p;
    writeln(ans);
end;

begin
    readln(n, m);
    if (n = 1) or (m = 1) then
        do1 else
    if (n = 2) or (m = 2) then
        do2 else
    if (n = 3) or (m = 3) then
        do3 else
    if (n = 4) or (m = 4) then
        do4 else
    begin
        work(1, 1);
        dfs(1, 1);
        writeln(ans);
    end;
end.

其实暴力可以优化
同样用 O ( 2 n m ) O(2^{nm}) O(2nm)枚举方案,用dp来验证
d p [ x ] [ y ] dp[x][y] dp[x][y]表示 ( x , y ) − > ( n , m ) (x,y)->(n,m) (x,y)>(n,m)的数值中最大,最小是多少
利用记忆化搜索,每个点都是要满足向右的最大小于等于向左的最小

#pragma GCC opimize("-Ofast")
#include <bits/stdc++.h>
#define maxn 10
#define LL long long
using namespace std;
struct data{
	int min, max;
}dp[maxn][maxn];
int a[maxn][maxn], n, m;
LL ans;

inline int read(){
	int s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

data check(int x, int y, int mx, int my){
	if (dp[x][y].min != 0) return dp[x][y];
	if (x == mx && y == my) return dp[x][y] = (data){a[x][y], a[x][y]};
	if (x == mx){
		data tmp = check(x, y + 1, mx, my);
		return dp[x][y] = (data){tmp.min + (a[x][y] << (mx + my - x - y)), tmp.max + (a[x][y] << (mx + my - x - y))};
	}
	if (y == my){
		data tmp = check(x + 1, y, mx, my);
		return dp[x][y] = (data){tmp.min + (a[x][y] << (mx + my - x - y)), tmp.max + (a[x][y] << (mx + my - x - y))};
	}
	data r = check(x, y + 1, mx, my);
	if (r.min == -1) return dp[x][y] = (data){-1, -1};
	data d = check(x + 1, y, mx, my);
	if (d.min == -1 || r.max > d.min) return dp[x][y] = (data){-1, -1};
	return dp[x][y] = (data){r.min + (a[x][y] << (mx + my - x - y)), d.max + (a[x][y] << (mx + my - x - y))};
}

void dfs(int x, int y){
	if (x > 1 && y == 1){
		memset(dp, 0, sizeof(dp));
		if (check(1, 1, x - 1, m).min == -1) return;
		if (x == n + 1){
			++ans; return;
		}
	}
	a[x][y] = 1;
	if (y == m) dfs(x + 1, 1); else dfs(x, y + 1);
	a[x][y] = 0;
	if (y == m) dfs(x + 1, 1); else dfs(x, y + 1);
}

void solve(){
	if (n > m) swap(n, m);
	dfs(1, 1);
//	printf("%lld\n", ans);
}

int main(){
//	freopen("in.in", "r", stdin);
//	n = read(), m = read();
	freopen("print.out", "w", stdout);
	for (int i = 1; i <= 4; ++i, puts(""))
		for (int j = 1; j <= 8; ++j){
			n = i, m = j, ans = 0;
			solve();
			printf("Ans[%d][%d] = %lld ", i, j, ans);
		}
	return 0;
}

然后暴力每枚举完一行就验证一遍,可以剪枝
然后可以把 n < = 8 , m < = 8 n<=8,m<=8 n<=8,m<=8范围内在10min内打表打出来
再花点时间可以得到 n = 8 , m = 9 n=8,m=9 n=8,m=9的答案
找规律发现 a n s n , n + 1 = 3 a n s n , n − 3 ∗ 2 n ( n > = 4 ) ans_{n,n+1}=3ans_{n,n}-3*2^n(n>=4) ansn,n+1=3ansn,n32n(n>=4)
3 a n s n , m = a n s n , m + 1 ( m > n ) 3ans_{n,m}=ans_{n,m+1}(m>n) 3ansn,m=ansn,m+1(m>n)

所以就做出来了

Code:

#include <bits/stdc++.h>
#define maxn 10
#define LL long long
using namespace std;
const LL qy = 1000000007;
int n, m;
int a[9][10] = {
	{0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 2, 4},
	{0, 0, 12, 36},
	{0, 0, 0, 112, 336},
	{0, 0, 0, 0, 912, 2688},
	{0, 0, 0, 0, 0, 7136, 21312},
	{0, 0, 0, 0, 0, 0, 56768, 170112},
	{0, 0, 0, 0, 0, 0, 0, 453504, 1360128},
	{0, 0, 0, 0, 0, 0, 0, 0, 3626752, 10879488}
};

LL ksm(LL n, LL k){
	LL s = 1;
	for (; k; k >>= 1, n = n * n % qy) if (k & 1) s = s * n % qy;
	return s;
}

int main(){
	scanf("%d%d", &n, &m);
	if (n > m) swap(n, m);
	if (n == 1) printf("%lld\n", ksm(2, m));
	else if (n + 1 >= m) printf("%lld\n", a[n][m]);
	else printf("%lld\n", a[n][n + 1] * ksm(3, m - n - 1) % qy);
	return 0;
}

接下来稍微用数学方法证明一下
首先发现两个重要性质
1.在这里插入图片描述
A < = B A<=B A<=B
于是
在这里插入图片描述从右上到左下,不减
2.在这里插入图片描述
每条斜线之间的方案相互独立
3.
在这里插入图片描述
此时 A > = B A>=B A>=B,结合 A < = B A<=B A<=B的必要条件,得到 A = B A=B A=B
所以对于在这里插入图片描述
只要出现两个这样的红色方块,就会出现一个子矩阵每条斜线内部必须相等的情况

然后这个情况非常常见
在这里插入图片描述第三条斜线就必须出现了

然后根据这个手推找规律,严谨的证明我还不会

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值