• Codeforces 1197E Culture Code DP


    题意:你有n个俄罗斯套娃,已知每个套娃的容积和体积,问有多少个子集满足以下条件:

    1:这个子集是一个极大子集,即不能再添加其它的套娃到这个子集里。

    2:子集的套娃之间的间隙和最小。

    思路1:线段树优化DP:

    首先把套娃按照容积为第一优先级,体积为第二优先级,从小到大排序。设ans[i].second为第i个套娃在最外层,间隙最小的极大子集的数目,ans[i].first为最小的间隙,我们来执行转移:假设第i个套娃的体积是r,容积比r大的第一个套娃的容积是l1, 体积是r1, 那么容易发现,所有容积在[l1, r1 - 1]范围内的套娃都可能由当前状态转移。我们可以通过线段树的特殊标记来执行转移,我在线段树维护了3个标记:容积,最小间隙,数目。在更新懒标记时,判断下放的最小间隙 - 容积是否比当前的小即可。这样通过线段树的单点查询就可以得到对应的DP状态。

    代码:

    #include <bits/stdc++.h>
    #define LL long long
    #define pLL pair<LL, LL>
    #define pii pair<int, int>
    #define ls (o << 1)
    #define rs (o << 1 | 1)
    using namespace std;
    const int maxn = 200010;
    const LL mod = 1000000007;
    pii a[maxn];
    int val[maxn];
    struct Seg {
    	pLL lz;
    	int pos;
    };
    Seg tr[maxn * 4];
    pLL ans[maxn];
    bool v[maxn];
    void maintain(int o, pLL tmp, int p) {
    	if(tmp.first - p < tr[o].lz.first - tr[o].pos) {
    		tr[o].lz = tmp, tr[o].pos = p;
    	} else if(tmp.first - p == tr[o].lz.first - tr[o].pos) {
    		tr[o].lz.second = (tr[o].lz.second + tmp.second) % mod;
    	}
    }
    void pushdown(int o) {
    	if(tr[o].lz.second != 0) {
    		maintain(ls, tr[o].lz, tr[o].pos);
    		maintain(rs, tr[o].lz, tr[o].pos);
    		tr[o].lz = make_pair(1e18, 0ll);
    		tr[o].pos = 0;
    	}
    }
    void build(int o, int l, int r) {
    	if(l == r) {
    		if(val[l]) {
    			tr[o].lz = make_pair(0, val[l]);
    			tr[o].pos = 0;
    		} else {
    			tr[o].lz = make_pair(1e18, 0);
    			tr[o].pos = 0;
    		}
    		return;
    	}
    	tr[o].lz = make_pair(1e18, 0);
    	tr[o].pos = 0;
    	int mid = (l + r) >> 1;
    	build(ls, l, mid);
    	build(rs, mid + 1, r);
    }
    pLL query(int o, int l, int r, int p) {
    	if(l == r) {
    		ans[l] = make_pair(tr[o].lz.first + a[l].first - tr[o].pos, tr[o].lz.second);
    		return ans[l];
    	}
    	pushdown(o);
    	int mid = (l + r) >> 1;
    	if(p <= mid) return query(ls, l, mid, p);
    	else return query(rs, mid + 1, r, p);
    }
    void update(int o, int l, int r, int ql, int qr, int p, pLL tmp) {
    	if(l >= ql && r <= qr) {
    		maintain(o, tmp, p);
    		return;
    	}
    	pushdown(o);
    	int mid = (l + r) >> 1;
    	if(ql <= mid) update(ls, l, mid, ql, qr, p, tmp);
    	if(qr > mid) update(rs, mid + 1, r, ql, qr, p, tmp);
    }
    int main() {
    	int n;
    	LL mi = 1e18;
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d%d", &a[i].second, &a[i].first);
    	}
    	sort(a + 1, a + 1 + n);
    	for (int i = 1; i <= n; i++) {
    		if(a[i].first >= mi) break;
    		val[i] = 1;
    		mi = min(mi, (LL)a[i].second);
    	}
    	build(1, 1, n);
    	mi = 1e18;
    	for (int i = 1; i <= n; i++) {
    		pLL tmp = query(1, 1, n, i);
    		ans[i] = tmp;
    		int pos1 = lower_bound(a + 1, a + 1 + n, make_pair(a[i].second, a[i].second)) - a;
    		int pos2 = lower_bound(a + 1, a + 1 + n, make_pair(a[pos1].second, a[pos1].second)) - a - 1;
    		if(pos1 <= n) 
    			update(1, 1, n, pos1, pos2, a[i].second, tmp);
    		if(pos1 <= n) {
    			v[i] = 1;
    		}
    	}
    	for (int i = 1; i <= n; i++) {
    		if(v[i]) continue;
    		mi = min(mi, ans[i].first);
    	}
    	LL res = 0;
    	for (int i = 1; i <= n; i++) {
    		if(v[i]) continue;
    		if(ans[i].first == mi) res = (res + ans[i].second) % mod;
    	}
    	printf("%lld
    ", res);
    }
    

    思路2:(来自PinkRabbit)刚才的做法虽然能过,但是对题目的性质发掘的不够完全。我们把思维转变一下:我们不妨看一下有哪些状态可能转移到当前状态,容易发现在排序之后能转移到当前状态的状态是一个前缀,那么我们在转移的时候可以采用2个指针这种做法。那么新的问题来了,怎么知道这些前缀状态中哪些是可以真正转移到当前状态呢?设f[i]为到第i个套娃的最小间隙,ans[i]为最小间隙的数目,a[i]为第i个套娃的体积,b[i]为容积,那么在那些前缀最状态中(假设是状态t),f[t] - a[t]最小的状态才可以像当前状态转移。所以在两个指针的过程中只需维护f[t] - a[t]的最小值即可,然后执行转移。

    代码:

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int maxn = 200010;
    const LL mod = 1000000007;
    int a[maxn], b[maxn], p1[maxn], p2[maxn];
    LL ans[maxn];
    int f[maxn];
    bool cmp1(int x, int y) {
    	return a[x] < a[y];
    }
    bool cmp2(int x, int y) {
    	return b[x] < b[y];
    }
    int main() {
    	int n, ed = -1;
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) {
    		scanf("%d%d", &a[i], &b[i]);
    		p1[i] = p2[i] = i;
    		ed = max(ed, b[i]);
    	}
    	sort(p1 + 1, p1 + 1 + n, cmp1);
    	sort(p2 + 1, p2 + 1 + n, cmp2);
    	int pos = 1;
    	int mi = 0, sum = 1;
    	for (int i = 1; i <= n; i++) {
    		int now = p2[i];
    		while(pos <= n && a[p1[pos]] <= b[now]) {
    			int t = p1[pos];
    			if(f[t] - a[t] < mi) {
    				mi = f[t] - a[t];
    				sum = ans[t];
    			} else if(f[t] - a[t] == mi) {
    				sum = (sum + ans[t]) % mod;
    			}
    			pos++;
    		}
    		f[now] = b[now] + mi;
    		ans[now] = sum;
    	}
    	LL res = 0;
    	mi = 1e9;
    	for (int i = 1; i <= n; i++) {
    		if(a[i] <= ed) continue;
    		if(f[i] < mi) {
    			mi = f[i];
    			res = ans[i];
    		} else if(f[i] == mi) {
    			res = (res + ans[i]) % mod;
    		}
    	}
    	printf("%lld
    ", res);
    }
    

      

  • 相关阅读:
    脚本——1-100的和
    脚本——删除文件为0大小的文件
    脚本——ping网址
    脚本——大于5k的文件有
    脚本——九九乘法表
    第十天:小数与随机数
    第九天:单元测试
    第八天:错误异常处理
    第七天(1):包与模块管理
    第七天(2):面向对象编程
  • 原文地址:https://www.cnblogs.com/pkgunboat/p/11231288.html
Copyright © 2020-2023  润新知