2024 年春节集训 _ 第二课 - 数据结构优化动态规划

【例题 \(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;
}
posted @ 2024-03-05 15:35  q(x)  阅读(2)  评论(0编辑  收藏  举报