[思路笔记] 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. 按钮

题目大意

image

思路

线段树模板。

posted @ 2023-02-06 20:45  shiranui  阅读(19)  评论(0编辑  收藏  举报
*/