codeforces 217E 【Alien DNA】
倒序考虑每一个操作,对于一个操作$[l, r]$,他产生的影响区间将是$[r+1,r + r + l - 1]$,如果$r+l-1>K$的话,$K$之后的区间我们是不关心的。
暴力扫描这个区间 然后对于每一个位置,维护一个类似于并查集的东西。
扫到位置$i$,定义数组$f$ 表示$i$这个位置上的字符是由$f_i$这个位置上的字符填充得到的。然后删除$i$。
这个东西具体怎么搞?我们可以弄一个长度为K,初始化为1的序列。获得第$i$个位置就是这个序列上第$i$个1,然后删除这个位置上的字符,就是把序列上该位置的1变成0。维护这个序列的话树状数组+二分或者树状数组+倍增花式搞,我选择后者,毕竟前者复杂度多一个$log$。不会的话可以看lyd那本书的P203(第一版),或者做一下poj 2182.
当然线段树维护也是可以的啦~
所以我们就得到如下算法:
倒序扫描每一个操作,维护一个变量$now$表示我们关心的区间末尾,对于每次操作的区间$[l,r]$,用一个变量$x$扫描$[r+1,r+l-1]$所要填充的字符的位置。每填充一个数$now$就减一,如果$r>now$就直接停止。因为$now$的缘故,最多填充次数为$K$。每次填充时,设$tmp$为01序列中第$r+1$个1,那么将$tmp$位置上的数减一,然后更新$f_{tmp}$为第$x$个1的位置
然后从左往右求解,如果$f_i$存在,那么$ans_i=ans_{f_i}$,不然$ans_i=s_{j+1},j++$ 由于$f_i$必然比$i$小 所以这样做一定是正确的
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; const int N = 3e6 + 10; char s[N], ans[N]; int n, K, len, f[N], c[N]; struct OPT{ int l, r;}a[5050]; int lowbit(int x){ return x & -x;} void add(int x, int val){ for(int i = x; i <= K; i += lowbit(i)) c[i] += val; } int get(int x){ int l = log2(K), sum = 0, ans = 0; for(int i = l; i >= 0; i--){ int k = (1 << i); if(ans + k <= K && sum + c[ans + k] < x) sum += c[ans + k], ans += k; } return ans + 1; } int main(){ scanf("%s", s + 1); scanf("%d%d", &K, &n); for(int i = 1; i <= K; i++) c[i] = i & -i; //初始化树状数组为1 for(int i = 1; i <= n; i++) scanf("%d%d", &a[i].l, &a[i].r); for(int now = K, i = n; i; i--) for(int x = a[i].l + 1, j = 1; a[i].r < now && j <= a[i].r - a[i].l + 1; x += 2, j++, now--){ if(x > a[i].r) x = a[i].l; int tmp = get(a[i].r + 1); add(tmp, -1), f[tmp] = get(x); } for(int i = 1, j = 0; i <= K; i++) ans[i] = f[i] ? ans[f[i]] : s[++j]; puts(ans + 1); return 0; }