牛跳房子(金) 在一个涂色的矩阵上由左上折跃至右下,每步的终点需在起点的严格右下方且颜色不同,求方案数模 10 亿零 7 之结果。
原文:
数构优化 DP。 下文有更新。
转移方程为 d p [ i ] [ j ] = ∑ 1 ≤ i ′ ≤ i ∑ 1 ≤ j ′ ≤ j d p [ i ′ ] [ j ′ ] dp[i][j] = \sum\limits_{1\le i' \le i}\sum\limits _{1\le j' \le j}dp[i'][j'] dp[i][j]=1≤i′≤i∑1≤j′≤j∑dp[i′][j′],但显然不能 O ( n 4 ) \mathrm O (n^4) O(n4) 暴力,二维线段树也有压力。我们先按行扫描,同时维护当前行以左区域内每列的转移方法数(总和以及每种颜色)。则转移时只需查询区间和即可,转移后需要及时写入。由于相当于滚动数组,列需要倒着扫。另外本题卡树状数组和一般的线段树,需要用动态开点线段树。
#include <cstdio>
#define F(z, u, v) for(int z = (u), dest##z = (v); z <= dest##z; z++)
#define Fd(z, u, v) for(int z = (u), dest##z = (v); z >= dest##z; z--)
#define Mid(u, v) ((u) + (v) >> 1)
typedef long long LINT;
const int MAXN = 751, MOD = 1000000007;
int r, c, k, s[MAXN][MAXN], dp[MAXN][MAXN];
namespace Tsg {
const int MAXT = 10000001;
int cnt = 0, w[MAXT], lc[MAXT], rc[MAXT], rt[MAXN * MAXN];
int InAdd(int rt, int k, int p, int lr = 1, int rr = c) {
if(k < lr || k > rr) return rt; if(!rt) rt = ++cnt;
w[rt] = (LINT(w[rt]) + p) % MOD; if(lr == rr) return rt;
lc[rt] = InAdd(lc[rt], k, p, lr, Mid(lr, rr));
rc[rt] = InAdd(rc[rt], k, p, Mid(lr, rr) + 1, rr);
return rt; }
int InSum(int rt, int l, int r, int lr = 1, int rr = c) {
return r < lr || l > rr || !rt? 0: l <= lr && rr <= r? w[rt]:
(LINT(InSum(lc[rt], l, r, lr, Mid(lr, rr)))
+ InSum(rc[rt], l, r, Mid(lr, rr) + 1, rr)) % MOD; }
inline void Add(int k, int p, int cl = 0) { rt[cl] = InAdd(rt[cl], k, p); }
inline int Sum(int k, int cl = 0) { return InSum(rt[cl], 1, k); }}
int main() {
scanf("%d%d%d", &r, &c, &k);
F(i, 1, r) F(j, 1, c) scanf("%d", s[i] + j);
dp[1][1] = 1; Tsg::Add(1, 1); Tsg::Add(1, 1, s[1][1]);
F(i, 2, r) Fd(j, c, 2) {
dp[i][j] = Tsg::Sum(j - 1) - Tsg::Sum(j - 1, s[i][j]);
while(dp[i][j] < 0) dp[i][j] += MOD; while(dp[i][j] >= MOD) dp[i][j] -= MOD;
Tsg::Add(j, dp[i][j]), Tsg::Add(j, dp[i][j], s[i][j]); }
printf("%d", dp[r][c]); }
更新:
今天很不幸考到了 CDQ 分治,因此 0xis 华丽炸掉。
我们考虑以下分治策略:1)若只剩一行则跳过,因为只有两个不同的行才能构成转移。2)划定中点为前锋,左边状态累计至前锋,并进一步向右计算,亦即只考虑左边对右边影响。代码转自 qzp666 的博客。
// f[i][j] 代表方案数,t[i] 代表颜色 i 最后出现的时间,s[i] 代表颜色出现的几次(需减去)
void solve(int l,int r)
{
if(l==r) return;
int mid=(l+r)>>1;
solve(l,mid);//先处理左半区间
++Time;//记录更新的时间
all=0;//总方案数
for(int j=1;j<=m;++j)//按列循环
{
for(int i=r;i>=mid+1;--i)//先更新右区间防止左区间对右区间的干扰
{
if(t[a[i][j]]<Time) {t[a[i][j]]=Time;s[a[i][j]]=0;}//通过更新的时间不相等来重置s数组
f[i][j]=((f[i][j]+all-s[a[i][j]])%p+p)%p;
}
for(int i=l;i<=mid;++i)
{
if(t[a[i][j]]<Time) {t[a[i][j]]=Time;s[a[i][j]]=0;}//重置s数组
s[a[i][j]]=(s[a[i][j]]+f[i][j])%p;
all=(all+f[i][j])%p;
}
}
solve(mid+1,r);//递归处理右半区间
}