[思路笔记] 0112&0114
抽象抽象,懒得写了所以好多就打了线段树板子(虽然说确实是板子),凑活着看吧。
代码慢慢补吧QAQ。
0112G.子序列问题
题目大意
给定长度为 \(n\) 的正整数序列 \(A_1,A_2,...,A_n\),定义函数 \(f(l,r)\) 表示不可重集 \(\{A_l,A_{l+1},...,A_r\}\) 的大小,求 \(\sum_{l=1}^n\sum_{r=l}^n(f(l,r))^2 \mod 10^9+7\)。
思路
首先考虑,求 \(\sum_{l=1}^n\sum_{r=l}^nf(l,r) \mod 10^9+7\) 怎么做。
考虑每个数字分别计算贡献。
比如现在有个串 ..x..x.
,考虑 x
的贡献。
考虑枚举右端点 \(R\),那么:
- \(R\in[1,2]\) 时,没有
x
产生贡献。 - \(R\in[3,5]\) 时,第一个
x
产生贡献。 - \(R\in[6,7]\) 时,第二个
x
产生贡献。
那么是不是可以开一棵线段树,当 \(R\) 在 \(3\) 和 \(6\) 的时分别在线段树 \([1,3]\) 和 \([4,6]\) 上加一,然后每个 \(R\) 都加一遍 \([1,R]\) 就得到了答案。
稍微总结一下,开个数组 \(last\) 表示每个数字上一次出现的位置。枚举右端点 \(R\),线段树 \([last_{a_R}+1,R]\) 区间加一,然后答案累计 \(query(1,R)\) (query
为线段树查询)。
回到原题,\(\sum_{l=1}^n\sum_{r=l}^n(f(l,r))^2\) 又怎么求呢?
枚举右端点 \(R\),线段树 \(L\) 处的值为 \([L,x],x\in[L,R]\) 这些区间的答案之和。此时新加进来一位 \(R\),该怎么算到平方和中呢?
设 线段树当前区间的所有子区间的平方和 为 \(S\),线段树当前区间的所有子区间的数字种类数和 为 \(sum\),那么更新时 \((S+x)^2=S^2+2\cdot x\cdot sum+x\cdot(r-l+1)\),\(sum+=r-l+1\)。依次更新,于之前类似地统计答案即可。
0112H.永无乡
题目大意
编号 \(1,2,...,n\) 的点,每个点有点权,点权两两不同。有两种操作:
- 连接点 \(x\) 和 \(y\)。
- 询问点 \(x\) 所在连通块内点权第 \(k\) 小的点的编号。
思路
并查集维护可持久化权值线段树。
0112I. POI2011 Tree Rotations
题目大意
给定一棵二叉树,节点权值为 \(1\) 到 \(n\) 的排列。每次可以交换一个非叶子节点的左右子树,求任意次数交换后先序遍历得到的编号序列逆序对数量最少是多少。
思路
考虑节点 \(x\),显然子树内子树的改变不会影响 \(x\) 的结果,影响 \(x\) 的答案的是它两棵子树的先后顺序。\(x\) 的答案可以表示为 左儿子答案+右儿子答案+(左先右后的逆序对数,右先左后的逆序对数)的最小值。
以权值为点建线段树,那么第三段就可以表示为 \(\min(tr[tr[x].ls].cnt\times tr[tr[y].rs].cnt,tr[tr[x].rs].cnt\times tr[tr[y].ls].cnt)\)。其中 \(x\),\(y\) 表示根节点的两棵子树。
然后……好像就莫得了。
0112J. 01序列操作
题目大意
给一个 \(n\) 个数的01序列现在对于这个序列有五种变换操作和询问操作:
0 a b
把 \([a, b]\) 区间内的所有数全变成 \(0\)。1 a b
把 \([a, b]\) 区间内的所有数全变成 \(1\)。2 a b
把 \([a,b]\) 区间内的所有数全部取反,也就是说把所有的 \(0\) 变成 \(1\),把所有的 \(1\) 变成 \(0\)。3 a b
询问 \([a, b]\) 区间内总共有多少个 \(1\)。4 a b
询问 \([a, b]\) 区间内最多有多少个连续的 \(1\)。
对于每一种询问操作,给出回答。
思路
算板子吧,就是细节真的多。线段树记录:包含左端点的最长 \(0\)、包含右端点的最长 \(0\)、区间内最长 \(0\)、包含左端点的最长 \(1\)、包含右端点的最长 \(1\)、区间内最长 \(1\),然后就一通操作,搞定。
0114E. 跳跃求和
题目大意
给定序列 \(a\),\(q\) 次询问,每次给出 \(t\) 和 \(k\),求 \(a_t+a_{t+k}+a_{t+2\cdot k+...+a_{t+p\cdot k}}\)。
思路
离线,更号分治 \(k\)。
定一个阈值 \(T\),如果 \(k>T\),那么直接计算;如果 \(k\leq T\),那么就把询问按 \(k\) 排个序。
对于每一个 \(k\),从后往前dp,转移式为 \(f_i+=f_{i+k}\)。
代码
const int N = 300010;
int n, Q, a[N], t[N], k[N];
ll ans[N], f[N + 2000];
vector<int> q[N];
int main() {
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
Q = read();
for (int i = 1; i <= Q; i++) t[i] = read(), k[i] = read();
for (int i = 1; i <= Q; i++)
if (k[i] <= 2000)
q[k[i]].push_back(i);
else
for (int j = t[i]; j <= n; j += k[i]) ans[i] += a[j];
for (int i = 1; i <= 2000; i++) {
if (!q[i].size())
continue;
memset(f, 0, sizeof(f));
for (int j = n; j >= 1; j--) f[j] = f[i + j] + a[j];
for (int x : q[i]) ans[x] = f[t[x]];
}
for (int i = 1; i <= Q; i++) printf("%lld\n", ans[i]);
return 0;
}
0114F. 数组切割
题目大意
给一个长度为 \(n\) 的数组,可以将它切割成若干段,满足每段和都大于等于 \(0\),求方案数。
思路
搞一搞前缀和,开树状数组,每个节点表示一段区间,查询即可。
代码
const int N = 2000010, mod = 1e9 + 7;
int n, m, a[N];
ll sum[N], f[N];
int c[N];
void add(int x, int k) {
while (x <= n) c[x] = (c[x] + k) % mod, x += x & -x;
}
int ask(int x) {
int res = 0;
while (x) res = (res + c[x]) % mod, x -= x & -x;
return res;
}
int main() {
n = read();
for (int i = 1; i <= n; i++) {
int x = read();
a[i] = sum[i] = x + sum[i - 1];
}
sort(a, a + 1 + n);
m = unique(a, a + 1 + n) - a;
for (int i = 0; i <= n; i++) sum[i] = lower_bound(a, a + 1 + m, sum[i]) - a + 1;
add(sum[0], 1);
for (int i = 1; i <= n; i++) f[i] = ask(sum[i]), add(sum[i], f[i]);
printf("%d\n", f[n]);
return 0;
}
0114G. 序列操作
题目大意
现在有一个长度为 \(n\) 的序列。刚开始,序列中的数全部为 \(0\)。
接下来有 \(m\) 个操作,操作分为两种,格式如下:
-
A p
表示找最早出现的连续 \(p\) 个 \(0\) 的区间,将区间中的数全部改为 \(1\)。若找不到,则操作失败。 -
L a b
表示把 \([a,b]\) 这个区间清空为 \(0\)。
问第一种操作失败的次数。
思路
线段树模板。
代码
const int N = 300010;
int n, m, a[N];
struct SegmentTree {
int l[N << 2], r[N << 2], llen[N << 2], rlen[N << 2], mlen[N << 2], lazy[N << 2];
void update(int rt) {
llen[rt] = llen[rt << 1], rlen[rt] = rlen[rt << 1 | 1];
if (llen[rt << 1] == r[rt << 1] - l[rt << 1] + 1) llen[rt] += llen[rt << 1 | 1];
if (rlen[rt << 1 | 1] == r[rt << 1 | 1] - l[rt << 1 | 1] + 1) rlen[rt] += rlen[rt << 1];
mlen[rt] = max(max(max(llen[rt], rlen[rt]), rlen[rt << 1] + llen[rt << 1 | 1]), max(mlen[rt << 1], mlen[rt << 1 | 1]));
}
void pushdown(int rt) {
if (lazy[rt] == -1 || r[rt] - l[rt] < 1) return ;
lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt];
llen[rt << 1] = rlen[rt << 1] = mlen[rt << 1] = lazy[rt] ? 0 : r[rt << 1] - l[rt << 1] + 1;
llen[rt << 1 | 1] = rlen[rt << 1 | 1] = mlen[rt << 1 | 1] = lazy[rt] ? 0 : r[rt << 1 | 1] - l[rt << 1 | 1] + 1;
lazy[rt] = -1;
}
void build(int rt, int L, int R) {
l[rt] = L, r[rt] = R, lazy[rt] = -1;
if (L == R) {
llen[rt] = rlen[rt] = mlen[rt] = 1;
return ;
}
int mid = L + R >> 1;
build(rt << 1, L, mid), build(rt << 1 | 1, mid + 1, R);
update(rt);
}
void change(int rt, int L, int R, int p) {
if (r[rt] < L || R < l[rt]) return ;
if (L <= l[rt] && r[rt] <= R) {
llen[rt] = rlen[rt] = mlen[rt] = (!p) * (r[rt] - l[rt] + 1);
lazy[rt] = p;
return ;
}
pushdown(rt);
if (L <= r[rt << 1]) change(rt << 1, L, R, p);
if (l[rt << 1 | 1] <= R) change(rt << 1 | 1, L, R, p);
update(rt);
}
int query(int rt, int len) {
pushdown(rt);
if (mlen[rt << 1] >= len) return query(rt << 1, len);
else if (rlen[rt << 1] + llen[rt << 1 | 1] >= len) return l[rt << 1 | 1] + len - rlen[rt << 1] - 1;
else return query(rt << 1 | 1, len);
}
} tree;
int ans;
int main() {
n = read();
tree.build(1, 1, n);
m = read();
for (int I = 1; I <= m; I++) {
string op; cin >> op;
if (op == "A") {
int x = read();
if (tree.mlen[1] < x) {ans++; continue;}
int k = tree.query(1, x);
tree.change(1, k - x + 1, k, 1);
}
if (op == "L") {
int l = read(), r = read();
tree.change(1, l, r, 0);
}
}
printf("%d\n", ans);
return 0;
}
0114H. 按钮
题目大意
思路
线段树模板。