2024 年春節集訓 _ 第二課 - 資料結構最佳化動態規劃

ChihiroFujisaki發表於2024-03-09

【例題 \(1\) 】 遞增子序列

\(\color{white}{link}\)

考慮 \(dp.\)

\(dp[i][j]\) 表示以元素 \(i\) 為結尾,長度為 \(k\) 的方案數。

那麼顯而易見就有一個轉移方程:

\[dp[i][j]=\sum_{a[k]<a[i] ,\ k<i} dp[k][j-1] \]

先拋去第二維度的 \(j\) ,這是可以做一個關於 \(a[i]\) 值的大小,數值是 \(dp\) 的樹狀陣列的。

維護兩個操作:單點修改,區間查詢。

當然因為 \(a[i]\) 實在是太大了,所以離散化。

如果加上第二層維度那就建立 \(k\) 個樹狀陣列就好了。

程式
#include <bits/stdc++.h>
#define int long long
#define ls (u << 1)
#define rs (u << 1 | 1)
#define lson ls, l, mid
#define rson rs, mid + 1, r
#define mid ((l + r) >> 1)

const int inf = 1e18;
const int N = 1e4 + 10;
const int mod = 123456789;
using namespace std;
int n, m;
struct ta {
    int tree[N];
    inline int lowbit(int x) { return x & -x; }
    inline void update(int x, int k) {
        while (x <= N) (tree[x] += k) %= mod, x += lowbit(x);
    }
    inline int query(int x) {
        int res = 0;
        while (x) (res += tree[x]) %= mod, x -= lowbit(x);
        return res;
    }
} t[105];
int tmp, val[N], b[N];
signed main() {
    while (~scanf("%lld%lld", &n, &m)) {
        for (int i = 1; i <= m; ++i) memset(t[i].tree, 0, sizeof t[i].tree);
        memset(val, 0, sizeof val), memset(b, 0, sizeof b);
        tmp = 0;
        for (int i = 1; i <= n; ++i) scanf("%lld", &val[i]), b[i] = val[i];
        stable_sort(b + 1, b + n + 1);
        int len = unique(b + 1, b + n + 1) - b - 1;
        for (int i = 1; i <= n; ++i) val[i] = lower_bound(b + 1, b + len + 1, val[i]) - b;
        for (int i = 1; i <= n; ++i) {
            t[1].update(val[i], 1);
            for (int j = 1; j < m; ++j) {
                tmp = t[j].query(val[i] - 1);
                if (!tmp)
                    break;
                t[j + 1].update(val[i], tmp);
            }
        }
        printf("%lld\n", t[m].query(n) % mod);
    }
    return 0;
}

選做:

【練習 \(1\) 】 折線統計

\(\color{white}{link}\)

考慮 \(dp.\)

\(x\) 排序,

\(dp[i][j][0/1]\) 為以 \(i\) 位置結束,長度為 \(j\)\(0\) 上升 \(1\) 下降的方案數。則有

\[dp[i][j][0]=\sum _{a[k]<a[i],\ k<i } dp[k][j][0] + \sum _{a[k]<a[i],\ k<i } dp[k][j-1][1] \]

\[dp[i][j][0]=\sum _{a[k]>a[i],\ k<i } dp[k][j-1][0] + \sum _{a[k]>a[i],\ k<i } dp[k][j][1] \]

由於我是 \(Sach\),所以我不予證明。

注意因為 \(y\) 很大所以要離散化。

最佳化使用關於 \(a[i]\) 建立維護 \(dp\) 的樹狀陣列。沒有試過線段樹。

時間複雜度分析:

\(\mathcal{O(n)O(\log \alpha)O(m)=O(nm \log n)}\) 瓶頸在計算 \(dp\) 上。

不過足以透過本題。

程式
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,mod=1e5+7;
int n,k,mx;
struct fwt{ int tree[N];
	inline int lb(int x){ return x&-x; }
	inline void upd(int x,int k){ while(x<=mx) (tree[x]+=k)%=mod, x+=lb(x); }
	inline int q(int x){ int s=0; while(x) (s+=tree[x])%=mod, x-=lb(x); return s%=mod;}
}t1[12],t2[12];
struct pt{ int x,y; } a[N];
inline bool cmp(pt x,pt y){ return x.x<y.x; }
signed main(){
	scanf("%lld%lld",&n,&k), k++;
	for(int i=1;i<=n;++i) scanf("%lld%lld",&a[i].x,&a[i].y), mx=max(mx,a[i].y);
	stable_sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i){
		t1[1].upd(a[i].y,1), t2[1].upd(a[i].y,1);
		for(int j=2;j<=k;++j){
			int tmp1=(t1[j].q(a[i].y-1)+t2[j-1].q(a[i].y-1))%mod, tmp2=((t1[j-1].q(mx)-t1[j-1].q(a[i].y))%mod+(t2[j].q(mx)-t2[j].q(a[i].y))%mod+mod)%mod;
			if(!tmp1 && !tmp2) break;
			t1[j].upd(a[i].y,tmp1), t2[j].upd(a[i].y,tmp2);
		}
	}
	return printf("%lld\n",(t1[k].q(mx)+t2[k].q(mx))%mod),0;
}

相關文章