这道题有一个非常实用的考场策略在里面,就是暴力
当年考场里写了最慢的暴力,找出了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,n−3∗2n(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
所以对于
只要出现两个这样的红色方块,就会出现一个子矩阵每条斜线内部必须相等的情况
然后这个情况非常常见
第三条斜线就必须出现了
然后根据这个手推找规律,严谨的证明我还不会