把行从上到下
1
−
n
1-n
1−n标号,把对角线从左上到右下
1
−
(
n
+
m
−
1
)
1-(n+m-1)
1−(n+m−1)标号,则可以知道怎么样的染色方案为“合法“的:
1、第
i
i
i行不染色,则
i
i
i到
i
+
m
−
1
i+m-1
i+m−1号对角线不能都染色,不然会覆盖满一整行;
2、第
i
i
i条对角线染色,则
m
a
x
(
i
+
1
−
m
,
1
)
max(i+1-m,1)
max(i+1−m,1)到
m
i
n
(
n
,
i
)
min(n,i)
min(n,i)号横行(即这条对角线经过的横行)不能都染色,不然这条对角线染色没有用。
更抽象地,看作长为
n
n
n和
n
+
m
−
1
n+m-1
n+m−1的两个序列
A
,
B
A,B
A,B,每个数可以选或不选,
A
i
A_i
Ai不选则
B
i
B_i
Bi往后不能连续选超过
m
m
m个,
B
i
B_i
Bi选则
A
m
i
n
(
i
,
n
)
A_{min(i,n)}
Amin(i,n)往前不能连续选超过
m
i
n
(
n
,
i
)
−
m
a
(
i
+
1
−
m
,
1
)
+
1
min(n,i)-ma(i+1-m,1)+1
min(n,i)−ma(i+1−m,1)+1个。
发现任一序列
i
i
i处的限制总是和另一序列的
i
i
i处相关,则两序列同一位置一起转移,
B
B
B序列多出的部分再单独转移即可。
设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k表示转移到第
i
i
i位,
B
B
B序列从当前还能往后连续选
j
j
j个,
A
A
A序列到当前已经连续选了
k
k
k个,分
A
i
A_i
Ai选或不选,
B
i
B_i
Bi选或不选来转移。要注意在
i
>
n
i>n
i>n时,
k
k
k表示的是
A
A
A到
n
n
n已经连续选的个数,因为
B
B
B限制的是
A
m
i
n
(
i
,
n
)
A_{min(i,n)}
Amin(i,n)。
第一维可以滚动,时间复杂度
O
(
n
3
)
O(n^3)
O(n3)。
代码
#include<cstdio>#include<cstring>#include<algorithm>usingnamespace std;#define N 510int f[2][N][N];intmain(){int n, m, P, i, j, k;scanf("%d%d%d",&n,&m,&P);
f[0][m +1][0]=1;for(i =0; i <= n + m -2; i++){int o = i %2;memset(f[o ^1],0,sizeof(f[o ^1]));for(j =0; j <= m +1; j++){for(k =0; k <= n; k++)if(f[o][j][k]){if(i +1<= n){(f[!o][m +1][0]+= f[o][j][k])%= P;// a no b no(f[!o][m +1][k +1]+= f[o][j][k])%= P;// a yes b noif(j && m >1)(f[!o][min(m -2, j > m ? j : j -1)][0]+= f[o][j][k])%= P;//a no b yesif(j && k < i -max(i +1+1- m,1)+1)(f[!o][j > m ? j : j -1][k +1]+= f[o][j][k])%= P;//a yes b yes}else{(f[!o][m +1][k]+= f[o][j][k])%= P;// a no b noif(j && k < n -max(i +1+1- m,1)+1)(f[!o][j > m ? j : j -1][k]+= f[o][j][k])%= P;//a no b yes}}}}int ans =0;for(j =0; j <= m +1; j++)for(k =0; k <= n; k++)(ans += f[(n + m -1)%2][j][k])%= P;printf("%d\n", ans);return0;}