2019 ICPC Asia Xuzhou Regional H.Yuuki and a problem
题目链接
https://nanti.jisuanke.com/t/42547
题目大意
给你一个长度为 $n$ 的数列 $a_i$ , $q$ 次询问,每次修改一个数的值,或者询问区间 $[l, r]$ 中所有的数所不能组出来的最小的正整数。
题目分析
不大清楚这题标程咋写的为啥要 $15s$(现场是 $12s$?)……个人认为 $O(n\log^2n)$ 的算法还是比较明显的。
首先如果 $[1, x]$ 中的所有数都可以被组出来,那么对于任何一个 $y \leq x + 1$,都可以使 $[1, x + y]$ 中的所有的数被组出来。
这样显然如果维护查询区间的有序数列,只需要前缀扫一遍就可以得到答案了。但是如果这样用线段树维护的话显然复杂度还是很大。
可以发现有了上面这个结论了之后,显然一个数列可以分为若干个区间,每个区间表示如果这个区间最小的数可以被组成了,那么剩下的数也可以对答案产生贡献。也就是说每隔区间需要记录两个数据,一个是最小的数 $x$,一个是这个区间的总和 $sum$。两个区间 $(x_1, sum_1), (x_2, sum_2), x_1 \leq x_2$ 能合并当且仅当 $x_1 + sum_1 >= x_2$,合并后的新区间即为 $(x_1, sum_1 + sum_2)$。由于 $x \leq sum$ ,所以连个相邻的区间的最小值最少相差两倍,所以可以证明这些区间的数量是 $\log n$ 级别的。
这样就可以直接搞了,代码很短不难写,而且题目数据不强,一开始我的代码合并的时候判的是 $sum_1 + 1 >= x_2$ 也只跑了 $9493ms$,而事实上这样写区间的数量可以被卡到 $O(n)$ 级别,是不满足复杂度要求的。改为 $x_1 + sum_1 >= x_2$ 之后就只用了 $2093ms$。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 2e5 + 5; int n, m; long long a[maxn]; struct ex { vector <pair <long long ,long long> > vec; int init(long long x){ vec.clear(); vec.push_back(make_pair(x, x)); return 0; } }; ex merge(ex x, ex y){ ex ret; ex tmp; tmp.vec.resize(x.vec.size() + y.vec.size()); merge(x.vec.begin(), x.vec.end(), y.vec.begin(), y.vec.end(), tmp.vec.begin()); ret.vec.push_back(tmp.vec[0]); for(int i=1;i<tmp.vec.size();i++){ pair <long long, long long> p = tmp.vec[i]; if(ret.vec.back().second + ret.vec.back().first >= p.first){ ret.vec.back().second += p.second; }else{ ret.vec.push_back(p); } } return ret; } struct stnode { int l, r; int lc, rc; ex inf; }; struct st { stnode tree[maxn * 4]; int tc; int root; int init_tree(int l, int r){ int mid = (l + r) / 2; int pos = ++tc; tree[pos].l = l; tree[pos].r = r; if(l == r){ tree[pos].inf.init(a[l]); return pos; } tree[pos].lc = init_tree(l, mid); tree[pos].rc = init_tree(mid + 1, r); tree[pos].inf = merge(tree[tree[pos].lc].inf, tree[tree[pos].rc].inf); return pos; } ex query_tree(int pos, int l, int r){ int mid = (tree[pos].l + tree[pos].r) / 2; if(tree[pos].l == l and r == tree[pos].r){ return tree[pos].inf; }else{ if(r <= mid){ return query_tree(tree[pos].lc, l, r); }else if(l <= mid){ return merge(query_tree(tree[pos].lc, l, mid), query_tree(tree[pos].rc, mid + 1, r)); }else{ return query_tree(tree[pos].rc, l, r); } } } int update_tree(int pos, int p, long long x){ int mid = (tree[pos].l + tree[pos].r) / 2; if(tree[pos].l == tree[pos].r){ tree[pos].inf.init(x); }else{ if(p <= mid){ update_tree(tree[pos].lc, p, x); }else{ update_tree(tree[pos].rc, p, x); } tree[pos].inf = merge(tree[tree[pos].lc].inf, tree[tree[pos].rc].inf); } return 0; } long long query(int l, int r){ ex ret = query_tree(root, l, r); if(ret.vec[0].first != 1)return 1; return ret.vec[0].second + 1; } int update(int p, long long x){ update_tree(root, p, x); return 0; } int init(int x){ int i, j; tc = 0; root = init_tree(1, x); return 0; } } tree; int read(){ int x; scanf("%d", &x); return x; } int main(){ int i, j; int x, y; int opt; n = read(); m = read(); for(i=1;i<=n;i++){ a[i] = read(); } tree.init(n); while(m--){ opt = read(); x = read(); y = read(); if(opt == 1){ tree.update(x, y); }else{ printf("%lld\n", tree.query(x, y)); } } return 0; }