CF568E Longest Increasing Subsequence (dp)

题意

给一个长度为n的,有K个位置空缺的序列。
然后给出m个的候选数,用于填充空缺的地方。
输出一组使得填充后最长上升子序列最长的方案。
n ≤ 1 e 5 , k ≤ 1000 , k ≤ m ≤ 1 e 5 n \leq 1e5, k \leq 1000, k \leq m \leq 1e5 n1e5,k1000,km1e5

思路

  • 观察可以发现一个 O ( n 2 ) O(n^2) O(n2)的dp,尝试优化,调着调着之后发现fake掉了= =
  • 瞄了一下题解,发现是这个亚子的:
  • 考虑将所有候选数排倒序,然后插入每个空的地方,答案不会改变。
  • 由于空只有1000个,所以答案是容易使用dp求出的。
  • 注意这里要做到 O ( k n + n log ⁡ n ) O(kn+n\log n) O(kn+nlogn),所以使用 c [ i ] c[i] c[i]表示长度为i的最小结尾的方法。空的地方更新的时候两个指针扫一遍即可。
  • 假如使用 O ( k n ) O(kn) O(kn)的空间来存储转移前导。无法通过本题的128mb限制。
  • 那我们转而存每一个确定的数的前导, 并连续赋值一段LIS中的空位置。
  • 也就是假如当前位置x的前导是一个空,那么枚举前一个非空的位置y(别忘了必须满足 a [ y ] < a [ x ] a[y]<a[x] a[y]<a[x]),只要 [ x , y ] [x,y] [x,y]之间能够填数,使得LIS长度从 f [ y ] f[y] f[y]增加到 f [ x ] f[x] f[x]即可。然后将x变成y,继续操作(这也是 O ( n 2 ) O(n^2) O(n2)dp的想法,但是要优化这个dp需要三维偏序…)
  • 可以给序列前后加上正负无穷来避免繁琐的细节。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], b[N], s[N], tmp[N * 2], w[N * 2];
int m;

int js[N * 2], f[N], pre[N], c[N], from[N];
namespace test{
	int f[N];
	void check() {
		int ans=0;
		for(int i=1;i<=n;i++){
			for(int j=0;j<i;j++)if(a[i]>a[j]) {
				f[i]=max(f[i],f[j]+1);
			}
			ans=max(ans,f[i]);
		}
		cout<<ans - 2<<endl;
	}
}

void dp() {
	for(int i = 1; i <= n; i++) {
		if (a[i] == -1) {
			for(int j = m, zj = c[0]; j; j--) {
				while(c[zj] >= b[j] && zj > 0) zj--;
				if (zj == c[0]) {
					c[0]++;
					from[c[0]] = -1;
					c[c[0]] = b[j];
				} else if (b[j] < c[zj + 1]) {
					from[zj + 1] = -1;
					c[zj + 1] = b[j];
				}
			}
		} else {
			int z = lower_bound(c + 1, c + 1 + c[0], a[i]) - c;
			if (z == c[0] + 1) c[0]++;
			from[z] = i;
			c[z] = a[i];
			pre[i] = from[z - 1];
			f[i] = z;
		}
	}
}

void build_solution() {
	for(int now = n; now;) {
		if (pre[now] != -1) {
			now = pre[now];
		} else {
			for(int i = now - 1; i; i--) if (a[i] != -1) {
				if (a[i] < a[now] && f[now] - f[i] - 1 == min(s[now - 1] - s[i], w[a[now] - 1] - w[a[i]])) {
					for(int x = i + 1, zj = a[i] + 1; x < now; x++) if(a[x] == -1) {
						while(js[zj] == 0 && zj + 1 < a[now]) zj++;	
						if (js[zj]) {
							a[x] = zj;
							js[zj]--; zj++;
						}
					}
					now = i;
					break;
				}
			}
		}
	}
}

int main() {
	freopen("e.in", "r", stdin);
	cin>>n;
	a[n+2] = 1e9 + 1;
	for(int i = 1; i <= n + 2; i++){
		if(2 <= i && i <= n + 1) scanf("%d",&a[i]);
		s[i] = s[i - 1] + (a[i] == -1);
		if(a[i] != -1) tmp[++tmp[0]] = a[i];
	}
	n += 2;
	cin>>m;
	for(int i = 1; i <= m; i++){
		scanf("%d", &b[i]);
		tmp[++tmp[0]] = b[i];
	}
	sort(tmp + 1, tmp + 1 + tmp[0]);
	tmp[0] = unique(tmp + 1, tmp + 1 + tmp[0]) - tmp - 1;

	for(int i = 1; i <= n; i++) if (a[i] != -1) {
		a[i] = lower_bound(tmp + 1, tmp + 1 + tmp[0], a[i]) - tmp;
	}

	for(int i = 1; i <= m; i++) {
		b[i] = lower_bound(tmp + 1, tmp + 1 + tmp[0], b[i]) - tmp;
		w[b[i]] = 1;
		js[b[i]] ++;
	}
	for(int i = 1; i <= tmp[0]; i++) w[i] += w[i - 1];
	sort(b + 1, b + 1 + m);
	dp();
	build_solution();
	for(int i = 1, zj = 1; i <= n; i++) if(a[i] == - 1) {
		while(js[zj] == 0) zj++;
		a[i] = zj; js[zj]--;
	}
	for(int i = 2; i < n; i++) printf("%d ", tmp[a[i]]);
	// test :: check();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值