P3120 [USACO15FEB]牛跳房子(线段树优化$dp$($CDQ$分治))

${color{cyan}{>>Question}}$

很容易想到朴素方程,令$f[i][j]$表示到$i$行,$j$列的方案数

$$f[i][j] = sum f[k][l];(k<i,l<j,a[k][l] eq a[i][j])$$

但显然是$O(n^4)$的,其实这看起来就想二维偏序,但实际它有三个条件,可以$CDQ$分治(但我不会)

考虑吧方程换个形式,令$sum = sum f[k][l];(k<i,l<j)$,$x = sum f[k][l];(k<i,l<j,a[k][l] = a[i][j])$

$$f[i][j] = sum - x$$

(类似那道染色的思想)

可以一行一行地推(就能不用二维线段树,只用一维),令$sum[i]$表示$(1,1)$到上一行$i$列的矩形区域的前缀和(原本是二维,现在只用一维)

对于每种标记开一颗线段树,即开$k$颗线段树,这时便需要动态开点

时间复杂度 $O(n*m*log m)$

空间复杂度 $O(k*log m)$

代码如下

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 #define ll long long
 6 using namespace std; 
 7 
 8 template <typename T> void in(T &x) {
 9     x = 0; T f = 1; char ch = getchar();
10     while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
11     while( isdigit(ch)) {x = 10 * x + ch - 48; ch = getchar();}
12     x *= f;
13 }
14 
15 template <typename T> void out(T x) {
16     if(x < 0) x = -x , putchar('-');
17     if(x > 9) out(x/10);
18     putchar(x%10 + 48);
19 }
20 //-------------------------------------------------------
21 
22 const int N = 800,mod = 1e9+7;
23 
24 int n,m,k;
25 int a[N][N];ll f[N][N],sum[N];
26 int cnt,rt[N*N];
27 struct node {
28     int lc,rc; ll sum;
29 }t[6000000];
30 
31 void A(int &u,int l,int r,int pos,int add) {
32     if(!u) u = ++cnt;
33     if(l == r) {t[u].sum = (t[u].sum+add)%mod; return;}
34     int mid = (l+r)>>1;
35     if(pos <= mid) A(t[u].lc,l,mid,pos,add);
36     else A(t[u].rc,mid+1,r,pos,add);
37     t[u].sum = (t[t[u].lc].sum + t[t[u].rc].sum)%mod;
38 }
39 
40 ll Q(int u,int l,int r,int a,int b) {
41     if(!u) return 0;
42     if(a <= l && b >= r) return t[u].sum;
43     int mid = (l+r)>>1,res = 0;
44     if(a <= mid) res = (res + Q(t[u].lc,l,mid,a,b))%mod;
45     if(b > mid) res = (res + Q(t[u].rc,mid+1,r,a,b))%mod;
46     return res;
47 }
48 
49 int main() {
50     freopen("0.in","r",stdin);
51     int i,j;
52     in(n); in(m); in(k);//
53     for(i = 1;i <= n; ++i) for(j = 1;j <= m; ++j) in(a[i][j]);//
54     f[1][1] = 1; A(rt[a[1][1]],1,m,1,1); 
55     //sum[1] = 1;
56     for(i = 1;i <= m; ++i) sum[i] = 1;//
57     for(i = 2;i <= n; ++i) {
58         for(j = 2;j <= m; ++j) {
59             f[i][j] = (sum[j-1] - Q(rt[a[i][j]],1,m,1,j-1))%mod;
60             //A(rt[a[i][j]],1,m,j,f[i][j]);//debug 当前的线段树保存的是上一行的信息,要推完这一行才能更新 
61         }
62         ll tmp = 0;
63         for(j = 2;j <= m; ++j) {
64             tmp = (tmp + f[i][j])%mod;
65             sum[j] = (sum[j] + tmp)%mod;
66             A(rt[a[i][j]],1,m,j,f[i][j]);//right
67         }
68     }
69     out((f[n][m]+mod)%mod);
70     return 0;
71 }
原文地址:https://www.cnblogs.com/mzg1805/p/11398123.html