A.
状压 dp
。考虑到合并和分裂是互逆操作,所以只需要考虑合并操作。
trick:
将 b[i]
设置为 -b[i]
,题目转化为对一个序列,找到尽量多的和为 0
的集合。暴力转移是 O(3^(n+m))
,我们考虑优化转移。
注意到 sum=0
时贡献加一,可以设计出如下转移方程 :
dp[i]=max(dp[j]+dp[i-j])+(sum==0),j \in i
时间复杂度 O((n+m)2^(n+m))
。 实际优化并不大。
B.
题意:给定一个序列,有 m
次操作,每次选择数对 (a[i],a[j]),i!=j
且不重复,价值为 a[i]^a[j]
,求最大价值总和。 n<=5e4,m<=n^2
。
Solution:
比较棘手的一道题。
不难想到 Trie树
,但是我 Trie
没学好。
那么这道题很像最大异或路径。想想我们是怎么做的:枚举 a[i]
,然后在 Trie
树上查找路径,时间复杂度 O(nlogn)
,前提是每次只跳一边。
方便起见,我们令 m*2
,答案除以 2
即可。二分第 m*2
大值 mid
,计算出异或值 >=mid
的数对个数,这部分时间复杂度 O(nlog^2n)
。
最后来计算答案。对于 >=m*2
的部分,每一个减去 mid
即可。这里维护以 i
为子树,第 j
位为 1
的数的个数,时间复杂度 O(nlog^2n)
。
综上,我们用 Trie
树在 O(nlog^2n)
时间内过了此题。
C.
题意:给定一个序列,每次修改操作改变一个数的值,查询操作
询问 [l,r]
能否组成等差序列。 n,m<=3e5
。
solution:
我们考虑如何确定一个等差数列,等价于:
- 任意
i!=j
,a[i]!=a[j]
- 设等差序列最大值为 Max,最小值为 Min,则满足
(Max-Min)/k==r-l
gcd(a[2]-a[1],a[3]-a[2],..,a[n]-a[n-1])==d
于是乎,我们可以用两个线段树维护区间最大最小值,和区间 gcd
。
对于判重的情况,我们 将值相同的数放入同一 set
,每次 lower_bound
查询最近的值相同的数 <l
即可。
#include <bits/stdc++.h>
#define PII pair<int, int>
#define int long long
using namespace std;
inline int read() {
int X = 0;
bool flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = 0;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
X = (X << 1) + (X << 3) + ch - '0';
ch = getchar();
}
if (flag)
return X;
return ~(X - 1);
}
const int mx = 3e5 + 5;
int n, m, xo, num, a[mx], t2[mx << 2];
set<int> s[mx * 2];
set<int>::iterator it;
map<int, int> ID;
int gcd(int x, int y) { return (y == 0) ? x : gcd(y, x % y); }
struct SegmentTree {
int val, Min, Max;
} t[mx << 2];
int id(int x) {
if (!ID[x])
ID[x] = ++num;
return ID[x];
}
int getpre(int x, int y) {
it = s[id(y)].lower_bound(x);
if (it == s[id(y)].begin())
return 0;
return *--it;
}
void up(int p) {
t[p].val = max(t[p << 1].val, t[p << 1 | 1].val);
t[p].Min = min(t[p << 1].Min, t[p << 1 | 1].Min);
t[p].Max = max(t[p << 1].Max, t[p << 1 | 1].Max);
}
void update(int p, int l, int r, int x, int y) {
if (l == r) {
t[p].Min = t[p].Max = y;
t[p].val = getpre(x, y);
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
update(p << 1, l, mid, x, y);
else
update(p << 1 | 1, mid + 1, r, x, y);
up(p);
}
void update2(int p, int l, int r, int x, int y) {
if (l == r) {
t2[p] = y;
return;
}
int mid = (l + r) >> 1;
if (x <= mid)
update2(p << 1, l, mid, x, y);
else
update2(p << 1 | 1, mid + 1, r, x, y);
t2[p] = gcd(t2[p << 1], t2[p << 1 | 1]);
}
SegmentTree query(int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return t[p];
int mid = (l + r) >> 1;
if (qr <= mid)
return query(p << 1, l, mid, ql, qr);
if (mid < ql)
return query(p << 1 | 1, mid + 1, r, ql, qr);
SegmentTree t1 = query(p << 1, l, mid, ql, qr), t2 = query(p << 1 | 1, mid + 1, r, ql, qr), t3;
t3.Max = max(t1.Max, t2.Max);
t3.Min = min(t1.Min, t2.Min);
t3.val = max(t1.val, t2.val);
return t3;
}
int query2(int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return t2[p];
int mid = (l + r) >> 1;
if (qr <= mid)
return query2(p << 1, l, mid, ql, qr);
if (mid < ql)
return query2(p << 1 | 1, mid + 1, r, ql, qr);
int t1 = query2(p << 1, l, mid, ql, qr), t2 = query2(p << 1 | 1, mid + 1, r, ql, qr);
return gcd(t1, t2);
}
signed main() {
freopen("grade.in", "r", stdin);
freopen("grade.out", "w", stdout);
n = read(), m = read();
for (int i = 1; i <= n; i++)
a[i] = read(), s[id(a[i])].insert(i), update(1, 1, n, i, a[i]),
update2(1, 1, n, i, abs(a[i] - a[i - 1]));
for (int i = 1; i <= m; i++) {
int op = read();
if (op == 1) {
int x = read(), y = read();
x ^= xo, y ^= xo;
if (a[x] == y)
continue;
s[id(a[x])].erase(x);
a[x] = y;
s[id(y)].insert(x);
update(1, 1, n, x, y);
update2(1, 1, n, x, abs(a[x] - a[x - 1]));
if (x + 1 <= n)
update2(1, 1, n, x + 1, abs(a[x + 1] - a[x]));
} else {
int l = read(), r = read(), k = read();
l ^= xo, r ^= xo;
if(l > r) swap(l, r);
if (l == r) {
printf("Yes\n");
xo++;
continue;
}
if (k == 0) {
SegmentTree tmp = query(1, 1, n, l, r);
if (tmp.Min == tmp.Max) {
printf("Yes\n");
xo++;
} else {
printf("No\n");
}
continue;
}
if (query2(1, 1, n, l + 1, r) != k) {
printf("No\n");
continue;
}
SegmentTree tmp = query(1, 1, n, l, r);
if (tmp.val < l && (tmp.Max - tmp.Min) / k == r - l) {
printf("Yes\n");
xo++;
} else {
printf("No\n");
}
}
}
}
D.
题意:有一个 n
序列,初始全为空,每次操作选取两个不同的数 +1
,求 n
次操作后序列全为 2
的方案数。 n<=1e7
。
Solution:
该解法由巨佬 dj
提供。
经典递推。多项式数列。
设 dp[i]
表示 i
个数 i
次操作后全为 2
的方案数,f[i]
表示当前状态已有 2
个位置是 1
,i-1
次后的方案数。f
数组为辅助数组,。
我们转移的目标 : 先消除某一个格子。
dp[i-2] * (i-2) * C(i,2)
,即 i
与 j
匹配。

f[i-1] * C(i-1,2) * A(i,2)
,即 i
分别与· j,k
匹配。

f[]
数组转移:
dp[i-3] * (i-2) * A(i-1,2)
,即· j,k
分别与 i
匹配。

2. dp[i-2] * (i-1)
,即· j,k
互消。

3. f[i-2] * A(i-1,2) * A(i-2,2)
,注意配对有顺序。
综上,时间复杂度 O(n)
。
总结:这种题的关键是抽象出模型,没有清晰的状态定义时最好不要侥幸推转移方程。必要时待定系数猜式子也是一法。(详见巨佬 ljs
)。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」