氧化镍即 N i O NiO NiO,为 N O i NOi NOi 的同分异构体。
题目
题目描述
有一个
n
n
n 行
m
m
m 列的棋盘,最初都是白格(显然这不是国际象棋)。你可以给棋盘染色,将一整行全部染成黑色,或者将一整个对角线(从左下到右上)染成黑色。
请问,有多少种可能的最终局面?输出对 p p p 取模(不保证是质数)。
数据范围与提示
n
,
m
≤
500
n,m\le 500
n,m≤500 且
1
0
8
≤
p
≤
1
0
9
+
7
10^8\le p\le 10^9+7
108≤p≤109+7 。
思路
直接考虑最终局面并不容易。我们应该考虑染色方案。
我们可以优先涂行(或者优先涂对角线)。那么选择了一些行被染色之后,就是对于对角线的决策了。
我的思考是,有三个方面:因为是优先涂行,所以对角线不能填满一行,即不能连续涂 m m m 个对角线;有的行已经涂了,所以在它这里没有 “不能连续涂 m m m 个” 的限制;有的对角线已经被涂满,即连续涂了 m m m 行,不能再决策。
好像还挺麻烦的。我甚至考虑了一下网络流,然后得到了零分的高分。正解却基本是一个思路,只是换了一个方面:找不到已经涂满的对角线,可以找没涂满的对角线。那么我们就要找没涂的行。每个没涂的行对应的对角线是 [ l i , r i ] [l_i,r_i] [li,ri],一个区间。那么所有区间的并集就是可以决策的对角线。
很巧妙的是,这同时也告诉了我们什么行是不能被涂满的,以及它什么时候被涂满。问题已经转化为了:选择若干个区间,在这些区间的并集中选择一些元素,并且每个区间的元素都不能被全选,求方案数。
把区间按照右端点排序(虽然事实上它们天然就是按照右端点有序的)。设计一个 d p \tt dp dp,令 f ( i , j ) f(i,j) f(i,j) 表示考虑了前 i i i 个区间(考虑了 [ 1 , R i ] [1,R_i] [1,Ri] 的元素),最后一个没选的元素是 R i − j R_i-j Ri−j,其方案数。转移时,枚举选择的上一个区间 k k k,当 R i − j ≤ R k R_i-j\le R_k Ri−j≤Rk 时, k k k 对应的最后一个没选的元素就直接得到了;否则 k k k 对应的最后一个没选的元素可以任选。转移系数是 2 ? 2^? 2?,即元素的自由选择。
上面这个 d p \tt dp dp 是否很容易理解呢?其实 i , j i,j i,j 类似于两个指针,一个是区间(限制),另一个是空格(用来满足限制的)。二者不断往前推进,然后时刻满足条件,就这么简单。
复杂度 O ( n 3 ) \mathcal O(n^3) O(n3) 。
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 505;
int dp[MaxN][MaxN<<1];
int powtwo[MaxN<<1], s[MaxN];
int main(){
int n = readint(), m = readint();
int Mod = readint();
rep(i,powtwo[0]=1,n+m)
powtwo[i] = (powtwo[i-1]<<1)%Mod;
rep(i,1,n) drep(j,m-1,0){
dp[i][j] = powtwo[m-1-j]; // first range
rep(k,1,i-1) if(i-k > j){
int fre = powtwo[min(i-k,m)-j-1];
dp[i][j] = (dp[i][j]+1ll*s[k]*fre)%Mod;
}
else dp[i][j] = (dp[i][j]+dp[k][j+k-i])%Mod;
s[i] = (s[i]+dp[i][j])%Mod;
}
int ans = 1; // no range chosen
rep(i,1,n) ans = (ans+s[i])%Mod;
printf("%d\n",ans);
return 0;
}