题解CF193D Two Segments
Posted 顺心顺意 4122
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解CF193D Two Segments相关的知识,希望对你有一定的参考价值。
题意
给定一个\\(1\\sim N\\)的排列,在这个排列中选出两段互不重叠的区间,求使选出的元素排序后构成公差为1的等差数列的方案数。选出的两段区间中元素构成的集合相同时视为同一种方案。\\(1\\le N\\le 3\\times 10^5\\)。
分析
如果考虑怎么优化枚举的两个区间的话,发现不太好搞(反正我只会暴力)。
于是考虑枚举连续的值域区间,再判断一下连续的值域区间是由原排列中几段连续的区间构成,如果 \\(\\le 2\\),就是可行的方案。
对于这种区间问题,一般套路是确定一个点,然后对其他点算贡献。
设\\(f[l][r]\\) 表示值域 \\([l,r]\\) 是由几段构成的,\\(pos[i]\\) 表示 \\(i\\) 这个值在原序列的位置,我们从 \\(1\\) 到 \\(n\\) 依次枚举右端点 \\(i\\),考虑从 \\(i - 1\\) 转移到 \\(i\\),那如何在 \\(O(i)\\) 的时间内转移呢?可以找到如下规律:
- 如果原序列中 \\(i\\) 在的位置左右两个数都 \\(\\le i\\) ,那么肯定在之前加入了,而现在加入 \\(i\\) 会使 \\([l, i], l \\in [1, \\min(a[pos[i]-1], a[pos[i] + 1])]\\) 值域的段数 \\(-1\\),\\([l, i], l \\in (\\min(a[pos[i]-1], a[pos[i] + 1]), \\max(a[pos[i]-1], a[pos[i] + 1])]\\)值域的段数不变,\\([l, i], l \\in (\\max(a[pos[i]-1], a[pos[i] + 1]), i - 1]\\) 值域的段数 \\(+1\\)。
- 如果只有一个,设那个数的位置为 \\(x\\),那么对于 \\([l, i] ,l\\in [1, x]\\)值域的段数不变,\\([l, i], l \\in (x, i - 1]\\) 的段数 \\(+1\\)。
- 如果没有,那么 \\([l, i] ,l\\in i - 1\\)的值域的段数会 \\(+1\\)。
这样是 \\(O(n ^ 2)\\),区间加,区间减,很容易想到用线段树优化。
设枚举\\(i\\),线段树的区间 \\([l,r]\\),表示 \\([x, i], x\\in [l,r]\\) 的各种信息。
我们需要线段树维护区间的最小段数的值,是这个最小段数的值的区间个数,和是次小段数这个值的区间个数。
最后询问$1∼i−1 $需要分的段数是否小于等于 \\(2\\) 即可
如何维护详见代码注释。
#include<bits/stdc++.h>
#define N 300005
#define int long long
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
int read()
int x = 0, f = 1; char ch = getchar();
while(ch < \'0\' || ch > \'9\')if(ch == \'-\') f = -f; ch = getchar();
while(ch >= \'0\' && ch <= \'9\')x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();
return x * f;
int n, ans;
int a[N], pos[N];
int minn[N << 2], cnt0[N << 2], cnt1[N << 2], lazy[N << 2];
struct Segment
void pushup(int u)
minn[u] = min(minn[ls], minn[rs]);
cnt0[u] = (minn[ls] == minn[u]) * cnt0[ls] + (minn[rs] == minn[u]) * cnt0[rs];
cnt1[u] = (minn[ls] == minn[u]) * cnt1[ls] + (minn[ls] == minn[u] + 1) * cnt0[ls];
cnt1[u] += (minn[rs] == minn[u]) * cnt1[rs] + (minn[rs] == minn[u] + 1) * cnt0[rs];
//如果左/右区间的最小值等于整个区间的最小值,那么左/右区间次小值就是整个区间的次小值,统计个数
//如果左/右区间的最小值等于整个区间的最小值 + 1,那么最小值就是整个区间的次小值,因为每次枚举 $i$ 时,
//值的变化最多加减1,所以次小值就是最小值 + 1。
void pushdown(int u)
minn[ls] += lazy[u], lazy[ls] += lazy[u];
minn[rs] += lazy[u], lazy[rs] += lazy[u];
lazy[u] = 0;
void build(int u, int l, int r)
if(l == r) return cnt0[u] = 1, void(); //初始都为 1
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
void update(int u, int l, int r, int L, int R, int val)
if(L <= l && r <= R) return minn[u] += val, lazy[u] += val, void();
pushdown(u);
int mid = (l + r) >> 1;
if(L <= mid) update(ls, l, mid, L, R, val);
if(R > mid) update(rs, mid + 1, r, L, R, val);
pushup(u);
int query(int u, int l, int r, int L, int R)
if(L <= l && r <= R) return cnt0[u] * (minn[u] <= 2) + cnt1[u] * (minn[u] <= 1);
//如果最小值小于等于 2 ,说明最小值是符合的,统计进去。
//如果最小值小于等于 1 , 次小值 = 最小值 + 1 , 次小值也符合。
pushdown(u);
int mid = (l + r) >> 1, res = 0;
if(L <= mid) res += query(ls, l, mid, L, R);
if(R > mid) res += query(rs, mid + 1, r, L, R);
return res;
tr;
signed main()
n = read();
for(int i = 1; i <= n; ++i) a[i] = read(), pos[a[i]] = i;
tr.build(1, 1, n);
for(int i = 1; i <= n; ++i)
tr.update(1, 1, n, 1, i, 1);
if(a[pos[i] - 1] < i && a[pos[i] - 1]) tr.update(1, 1, n, 1, a[pos[i] - 1], -1);
if(a[pos[i] + 1] < i && a[pos[i] + 1]) tr.update(1, 1, n, 1, a[pos[i] + 1], -1);
if(i >= 2) ans += tr.query(1, 1, n, 1, i - 1);//[i,i]这段是不能算进去的
printf("%lld\\n", ans);
return 0;
题解 [ABC159F] Knapsack for All Segments
题目描述
求 (A) 的所有连续子段的 "子序列中元素的和等于 (S) 个数" 的和。
正解
求一个连续子段等于 (S) 的个数,可以用背包做到 (O(n))。
但要对于每一个区间做一次背包,复杂度实在过不去。
考虑一个子序列在 (A) 中产生的贡献(所有连续子段中的出现次数)。
子序列左端点是 (l),右端点是 (r) 的话,那么产生的贡献就是 (l imes (n - r + 1))。
发现这个贡献只与左右端点有关,那么就再做背包的时候魔改一下。
具体是这样实现的:
加入背包的时候,
-
如果当前没有放元素,那么乘上一个 (l) 的系数 (即加上 (l) 而不是加 1)。(左端点的贡献)
-
如果放入当前元素背包满了,那么对答案产生 (f_s imes (n - r + 1)) 的贡献。(右端点的贡献)
-
否则就按普通背包做就行了。
(color{DeepSkyBlue} {Code :})
#include <bits/stdc++.h>
#define N 3005
using namespace std;
const int mod = 998244353;
int n, S;
int a[N], f[N];
int main() {
scanf("%d %d", &n, &S);
for(int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int ans = 0;
for(int i = 1; i <= n; ++i) {
if(a[i] > S) continue;
else if(a[i] == S) {
ans = (ans + 1LL * i * (n - i + 1)) % mod;
} else {
ans = (ans + 1LL * f[S - a[i]] * (n - i + 1)) % mod;
for(int j = S; j > a[i]; --j)
(f[j] += f[j - a[i]]) %= mod;
f[a[i]] = (f[a[i]] + i) % mod;
}
}
printf("%d
", ans);
return 0;
}
以上是关于题解CF193D Two Segments的主要内容,如果未能解决你的问题,请参考以下文章
CF1108E1 Array and Segments (Easy version)(待更新)
CodeForces 193D. Two Segments线段树