2025/11/10~2025/11/16 做题笔记
2025/11/10
C. 圆环(circle)
感觉比 T2 要简单/kk
dp 状态是好想的,非常明显可以设 \(f_{i, j}\) 表示当操作完 \(i\) 操作,另一只手在 \(j\) 位置的最小代价。这个东西的状态转移方程也不难。
- 当由不是完成操作的手过来的时候 \(f_{i, p_{i - 1}} = \min f_{i - 1, p_{i - 1}} + Dist(p_{i - 1}, a_i)\)
- 当由是完成操作的手过来的时候 \(f_{i, j} = f_{i - 1, j} + Dist(a_{i - 1}, a_i)\)
显然当同一时间需要去两个位置的时候需要禁止第 2 种操作,所以其他位置的值应该设为 \(INF\)。这个东西显然是可以用线段树优化的,只需要维护 \(\min\limits_{l \leq j \leq r}f_{i, j}, \min\limits_{l \leq j \leq r}f_{i, j} - j, \min\limits_{l \leq j \leq r}f_{i, j} + j\) 这样就可以完成转移
区间赋值 tag 没清空,调了好久。标记下放一定要记得清空标记!!!
Code
#include <algorithm>
#include <iostream>
using namespace std;
using ll = long long;
const int kN = 3e5 + 1;
const ll kI = 1e15;
int c, n, m;
struct Op {
int t, p;
} op[kN];
struct Tr {
ll p, s, mn, add, clr;
} tr[kN * 4];
struct Info {
ll p, s;
};
Info Min(Info x, Info y) { return {min(x.p, y.p), min(x.s, y.s)}; }
void Func(int x, ll k) { tr[x].p += k, tr[x].s += k, tr[x].mn += k, tr[x].add += k; }
void Clear(int x) { tr[x].mn = tr[x].p = tr[x].s = kI, tr[x].clr = 1, tr[x].add = 0; }
void Pushdown(int x) {
if (tr[x].clr)
Clear(x * 2), Clear(x * 2 + 1);
Func(x * 2, tr[x].add), Func(x * 2 + 1, tr[x].add), tr[x].add = tr[x].clr = 0;
}
void ChkMin(int p, ll v, int x = 1, int l = 1, int r = n) {
if (l == r) {
v < tr[x].mn && (tr[x] = {v + l, v - l, v, 0, 0}, 0);
return;
}
Pushdown(x);
int m = (l + r) / 2;
if (p <= m)
ChkMin(p, v, x * 2, l, m);
else
ChkMin(p, v, x * 2 + 1, m + 1, r);
tr[x].mn = min(tr[x * 2].mn, tr[x * 2 + 1].mn), tr[x].p = min(tr[x * 2].p, tr[x * 2 + 1].p), tr[x].s = min(tr[x * 2].s, tr[x * 2 + 1].s);
}
void Build(int x = 1, int l = 1, int r = n) {
tr[x] = (l == 1 ? (Tr){1, -1, 0, 0, 0} : (Tr){kI, kI, kI, 0, 0});
if (l == r)
return;
int m = (l + r) / 2;
Build(x * 2, l, m), Build(x * 2 + 1, m + 1, r);
}
Info Query(int nl, int nr, int x = 1, int l = 1, int r = n) {
if (nl <= l && r <= nr)
return {tr[x].p, tr[x].s};
Pushdown(x);
int m = (l + r) / 2;
if (nr <= m)
return Query(nl, nr, x * 2, l, m);
if (nl > m)
return Query(nl, nr, x * 2 + 1, m + 1, r);
return Min(Query(nl, nr, x * 2, l, m), Query(nl, nr, x * 2 + 1, m + 1, r));
}
int Dist(int l, int r) {
(l > r) && (swap(l, r), 0);
return min(r - l, n - r + l);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> c >> n >> m;
for (int i = 1; i <= m; i++)
cin >> op[i].t >> op[i].p;
sort(op + 1, op + m + 1, [](Op& x, Op& y) { return x.t < y.t; });
Build(), op[0].p = n;
for (int i = 1; i <= m; i++) {
Info L = Query(1, op[i].p), R = Query(op[i].p, n);
ll v = min({op[i].p + L.s, n - op[i].p + L.p, R.p - op[i].p, n + R.s + op[i].p});
if (op[i].t == op[i - 1].t)
Clear(1);
else
Func(1, Dist(op[i - 1].p, op[i].p));
ChkMin(op[i - 1].p, v);
// cout << tr[1].mn << '\n';
}
cout << tr[1].mn;
return 0;
}
CF2056D Unique Median
一开始没有注意到 \(V \leq 10\)。。。
然后就十分简单了,显然是需要枚举值的。
首先想到 \(>v\) 和 $ < v$ 数量相等的偶长度区间是合法的,但是这并不是必要条件。于是不可行。考虑算不合法的偶长度区间。假设小的那个中位数为 \(a\),可以发现 \(\leq a\) 的数的个数恰好等于 \(\dfrac{len}{2}\)。那么不合法的偶长度区间的充要条件等价于存在一个 \(a\),使得小于等于 \(a\) 的数的个数恰好为 \(\dfrac{len}{2}\)。直接将小于等于 \(v\) 的数变成 1,然后开个桶算一下即可
Code
#include <iostream>
#include <map>
using namespace std;
using ll = long long;
const int kN = 1e5 + 1;
int T, n, a[kN];
map<int, int> mp[2], bef[2];
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
ll ans = 0;
for (int v = 1; v <= 10; v++) {
for (auto o : {0, 1})
mp[o].clear(), bef[o].clear();
bef[0][0] = 1;
for (int i = 1, s = 0; i <= n; i++) {
s += a[i] <= v;
int o = i & 1;
if (a[i] == v) {
for (int o : {0, 1}) {
for (auto [x, y] : bef[o])
mp[o][x] += y;
bef[o].clear();
}
}
ans += mp[o][2 * s - i];
bef[o][2 * s - i]++;
}
}
cout << 1ll * n * (n + 1) / 2 - ans << '\n';
}
return 0;
}
/*
0 0 1 0 0 0 0 1 0 0
*/
2025/11/11
A. 二分图
太唐了,一开始把第二个大样例的图画错了,以为偶度数的特殊性质想错了,重新读了题也没发现怎么错了。于是跳过了
由于欧拉回路,显然一个偶度数的连通块都可有直接走完。考虑对于两个奇数点,存在一条欧拉路径能把他处理掉。但是存在一种特殊情况,就是所有连通块中的奇数点都在同侧,但是不同连通块的奇数点存在在不同侧的情况,这样就会额外消耗代价
Code
#include <algorithm>
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
using ull = unsigned long long;
using i28 = __int128_t;
using ll = long long;
const int kN = 2e4 + 1;
int n, m, fl, cnt, ans, v[kN];
vector<int> e[kN];
void Dfs(int x) {
if (v[x])
return;
if (e[x].size() & 1)
fl |= x <= n ? 1 : 2;
v[x] = 1;
for (int i : e[x])
Dfs(i);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (int i = 1, x, y; i <= m; i++)
cin >> x >> y, e[x].push_back(y + n), e[y + n].push_back(x);
for (int i = 1; i <= 2 * n; i++)
cnt += (e[i].size() & 1) == 1;
ans += cnt / 2 - 1, cnt = 0;
for (int i = 1; i <= n; i++) {
if (!v[i] && e[i].size()) {
fl = 0, Dfs(i);
cnt |= fl == 3, ans += !fl;
}
}
if (cnt) {
cout << ans;
return 0;
}
cnt = 0;
for (int i = 1; i <= 2 * n; i++)
if (e[i].size() & 1)
cnt |= i <= n ? 1 : 2;
cout << ans + (cnt == 3) << '\n';
return 0;
}
B. 斜堆
好像没前面一题那么难。显然左子树一定是形如 \(k^a\),那么可以直接想到预处理 \(k^a\) 次方,那么就只会往一边走了。但是忘记了右子树也有字数大小限制,于是我以为他数量级下降很快,写了个递归斜挂了
实际上下降并不快,还需要处理左子树大小相同的情况,这种情况同意算即可
Code
#include <algorithm>
#include <cassert>
#include <iostream>
#include <unordered_map>
using namespace std;
using ull = unsigned long long;
using i28 = __int128_t;
const int kN = 51, kV = 71;
const ull kI = 1e18;
int q, m, fl, a[kN];
ull n, f[kN][kV];
i28 c[kN][kV];
int Find(int k, ull x) {
for (int i = 1; i < kV; i++)
if (c[k][i] + c[k][i - 1] > x)
return i - 1;
return -1;
}
ull Dfs(ull x, int k) {
if (x <= 1)
return x;
ull i = Find(k, x - 1), t = (x - c[k][i] - c[k][i - 1] - 1) / (c[k][i] + 1) + 1, rst = x - t * (c[k][i] + 1);
return t * f[k][i] + ull((i28)1 * t * (t + 1) / 2 * (c[k][i] + 1)) + Dfs(rst, k) + t * rst;
}
ull Calc(ull x, int k) {
if (a[k] == 1) {
ull h = __lg(x + 1) - 1, rst = x - ((1ll << (h + 1)) - 1), ans = 0;
for (ull it = 0; it <= h; it++)
ans += 1ull * (it + 1) * (1ull << it);
return ans + 1ull * rst * (h + 2);
}
return Dfs(x, k);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> m >> q;
for (int i = 1; i <= m; i++) {
cin >> a[i];
if (a[i] == 1)
continue;
c[i][0] = 1;
for (int j = 1; j < kV && c[i][j - 1] <= kI; j++)
c[i][j] = c[i][j - 1] * a[i];
for (int j = 0; j < kV && c[i][j]; j++)
f[i][j] = Calc(c[i][j], i);
}
fl = 1;
for (ull n; q--;) {
cin >> n;
ull ans = 0;
for (int i = 1; i <= m; i++)
ans += Calc(n, i);
cout << ans << '\n';
}
return 0;
}
2025/11/12
爬山中、调题中、写总结中
2025/11/13
CF1228E Another Filling the Grid
md 模数不对调了我好久
考虑前 \(i\) 列有 \(j\) 行已经填过了 1。那么考虑这一行新增填 \(c\) 个 1。
特判 \(c == 0\) 的情况:因为这一列需要有至少一个 1,所以需要减掉没有 1 的情况。或者直接钦定有 \(c\) 个 1。那么剩下的 \(n - c\) 个位置可以随便选:
否则,被覆盖的行可以随便选:
Code
#include <iostream>
using namespace std;
const int kN = 251, kM = 1e9 + 7;
int n, k, fac[kN], ifac[kN], f[kN][kN];
int Pow(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = 1ll * x * x % kM)
(y & 1) && (res = 1ll * res * x % kM);
return res;
}
int C(int n, int m) {
return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
fac[0] = ifac[0] = 1;
for (int i = 1; i < kN; i++)
fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
cin >> n >> k;
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n; j++) {
for (int c = 1; c <= j; c++)
f[i][j] = (f[i][j] + 1ll * f[i - 1][j] * C(j, c) % kM * Pow(k - 1, n - c) % kM) % kM;
for (int c = 1; c <= n - j; c++)
f[i][j + c] = (f[i][j + c] + 1ll * f[i - 1][j] * C(n - j, c) % kM * Pow(k, j) % kM * Pow(k - 1, n - j - c) % kM) % kM;
}
}
cout << f[n][n];
return 0;
}
还有更加优质的 \(n\log n\) 的做法
直接考虑容斥至少 \(i\) 列不合法。考虑计算一行符合条件的方案:不考虑一行合不合法,方案数为:\((k - 1)^i k^{n - i}\),其中 \((k - 1)^i\) 表示有 \(i\) 列不填 \(1\),剩下的可以任意填。但是现在需要让每一行都合法,那么减去 \((k - 1)^n\) 表示去掉全部不填 1 的方案数。因为有 \(n\) 行,所以乘 \(n\) 遍,再乘上一个组合系数 \(\dbinom{n}{i}\)。最终式子为
#include <iostream>
using namespace std;
const int kN = 1e6 + 1, kM = 1e9 + 7;
int n, k, fac[kN], ifac[kN];
int Pow(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = 1ll * x * x % kM)
(y & 1) && (res = 1ll * res * x % kM);
return res;
}
int C(int n, int m) {
return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
fac[0] = ifac[0] = 1;
for (int i = 1; i < kN; i++)
fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
cin >> n >> k;
int ans = 0;
for (int i = 0; i <= n; i++)
ans = (ans + ((i & 1) ? -1ll : 1ll) * C(n, i) * Pow((1ll * Pow(k - 1, i) * Pow(k, n - i) % kM - Pow(k - 1, n) + kM) % kM, n) % kM + kM) % kM;
cout << ans;
return 0;
}
A. 矩阵操作
菜完了,做了十万年。
很显然一个点肯定优先填同行和同列的位置,并且一个点只能填右下角的区域
考虑 \(nm\) 做法。行从上往下扫和列从左往右扫本质上是相同的,因为前面的多余部分都可以用,但是负数都只能算作代价。同理从下往上扫和从右往左是一样的。
思考从左往右扫应该怎么做:其实直接把前面多余的部分拿过来用即可。从右往左扫即是把后面需要填的部分拿到现在来填
考虑 \(nm\) 做法。一行行的考虑一定是对的,因为能填到下面的部分填到同行一定不劣,而同行的填不了以后都没机会填了。发现我需要填一定是用最近的多余的去填,因为更加前面的位置能覆盖的范围更大一些。于是使用一个栈可以维护。
接下来就是卡住我 1~2h 的东西。因为每次询问不能带 \(log\),我一直在想扫描线。但是发现扫描线根本不可做后重新思考询问清空 \(l~r\) 有什么性质。其实就相当于把中间部分删掉,然后把上半部分和下半部分拼接起来。那维护一个前缀和一个后缀再拼起来就做完了
Code
#include <iostream>
#include <numeric>
using namespace std;
using ll = long long;
const int kN = 5e3 + 2, kM = 5e2 + 2;
int n, m, q, st[kN], a[kN][kN];
ll pr[kN], su[kN], p[kN][kN], s[kN][kN];
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
p[i][j] = a[i][j] + max(p[i - 1][j], 0ll);
for (int j = 1, tp = 0; j <= m; j++) {
if (p[i][j] < 0) {
for (; tp > 0 && p[i][j] < 0; tp -= !p[i][st[tp]]) {
ll t = p[i][st[tp]];
p[i][st[tp]] -= min(t, -p[i][j]), p[i][j] += min(t, -p[i][j]);
}
} else
st[++tp] = j;
}
pr[i] = pr[i - 1];
for (int j = 1; j <= m; j++)
pr[i] += max(-p[i][j], 0ll);
}
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= n; j++)
s[i][j] = a[i][j] + min(s[i + 1][j], 0ll);
for (int j = 1, tp = 0; j <= m; j++) {
if (s[i][j] < 0) {
for (; tp > 0 && s[i][j] < 0; tp -= !s[i][st[tp]]) {
ll t = s[i][st[tp]];
s[i][st[tp]] -= min(t, -s[i][j]), s[i][j] += min(t, -s[i][j]);
}
} else
st[++tp] = j;
}
su[i] = su[i + 1];
for (int j = 1; j <= m; j++)
su[i] += max(s[i][j], 0ll);
}
ll cnt = 0;
for (int i = 1; i <= m; i++)
cnt += max(0ll, p[n][i]);
cout << pr[n] + cnt << '\n';
for (int l, r; q--;) {
cin >> l >> r, l--, r++;
ll ans = pr[l - 1] + su[r + 1], cnt[2] = {0, 0};
for (int i = 1; i <= m; i++) {
cnt[0] += p[l][i], cnt[1] += s[r][i];
if (cnt[1] < 0) {
ll t = cnt[0];
cnt[0] -= min(t, -cnt[1]), cnt[1] += min(t, -cnt[1]);
}
ans += max(-cnt[0], 0ll) + max(-cnt[1], 0ll), cnt[0] = max(cnt[0], 0ll), cnt[1] = max(cnt[1], 0ll);
}
// cerr << ans << ' ' << cnt[0] << ' ' << cnt[1] << '\n';
cout << ans + cnt[0] + cnt[1] << '\n';
}
return 0;
}
B. 无向稀疏图上更快的最长路径
可以快速发现有答案的点对最多大概在 \(O(m\log m)\) 左右,并且最长的路径顶多只有 \(\log m\) 长。但是暴力 dfs 的话枚举出边是非常容易爆炸的,考虑排序边权,如果已经乘炸了就退出。这个玄学东西竟然是对的,说明该优化的还是得优化,万一就过了呢
Code
#include <algorithm>
#include <cassert>
#include <iostream>
#include <map>
#include <queue>
#include <vector>
using namespace std;
using pii = pair<int, int>;
const int kN = 1e6 + 1;
int n, m, q;
vector<pii> e[kN];
map<int, int> mp[kN];
void Dfs(int t, int x, int s) {
mp[t][x] = max(mp[t][x], s);
for (pii i : e[x]) {
if (1ll * s * i.second > m)
break;
Dfs(t, i.first, s * i.second);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> q;
for (int i = 2, x, y; i <= m; i++)
cin >> x >> y, e[x].push_back({y, i}), e[y].push_back({x, i});
for (int i = 1; i <= n; i++)
sort(e[i].begin(), e[i].end(), [](pii x, pii y) { return x.second < y.second; });
for (int i = 1; i <= n; i++)
Dfs(i, i, 1);
for (int s, t; q--;) {
cin >> s >> t;
if (mp[s].find(t) == mp[s].end())
cout << "-1\n";
else
cout << mp[s][t] << '\n';
}
return 0;
}
2025/11/14
P5505 [JSOI2011] 分特产
对至少有 \(i\) 个同学没有分得特产容斥。那么对于每种特产,都有 \(n - i\) 个同学分特产。于是对于每一个 \(i\),都有方案数
最后配一个容斥系数即可
Code
#include <iostream>
using namespace std;
const int kN = 1e4 + 1, kM = 1e9 + 7;
int n, m, ans, a[kN], fac[kN], ifac[kN];
int Pow(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = 1ll * x * x % kM)
(y & 1) && (res = 1ll * res * x % kM);
return res;
}
int C(int n, int m) {
return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
fac[0] = ifac[0] = 1;
for (int i = 1; i < kN; i++)
fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> a[i];
for (int i = 0; i <= n; i++) {
int res = 1;
for (int j = 1; j <= m; j++)
res = 1ll * res * C(n + a[j] - i - 1, n - i - 1) % kM;
ans = (ans + (i & 1 ? -1ll : 1ll) * C(n, i) * res % kM + kM) % kM;
}
cout << ans;
return 0;
}
P6076 [JSOI2015] 染色问题
只有限制最多可以选择的颜色数是好算的,于是考虑计算最多选择 \(i\) 个颜色的方案数。然后这道题就变成了 CF1228E Another Filling the Grid。
Code
#include <iostream>
using namespace std;
const int kN = 1e5 + 1, kM = 1e9 + 7;
int n, m, c, ans, fac[kN], ifac[kN];
int Pow(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = 1ll * x * x % kM)
(y & 1) && (res = 1ll * res * x % kM);
return res;
}
int C(int n, int m) {
return 1ll * fac[n] * ifac[m] % kM * ifac[n - m] % kM;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
fac[0] = ifac[0] = 1;
for (int i = 1; i < kN; i++)
fac[i] = 1ll * fac[i - 1] * i % kM, ifac[i] = Pow(fac[i], kM - 2);
cin >> n >> m >> c;
for (int i = 0; i <= c; i++) {
int res = 0;
for (int j = 0; j <= n; j++)
res = (res + (j & 1 ? -1ll : 1ll) * C(n, j) * Pow((Pow(i + 1, n - j) - 1 + kM) % kM, m) % kM + kM) % kM;
ans = (ans + ((c - i) & 1 ? -1ll : 1ll) * C(c, i) * res % kM + kM) % kM;
}
cout << ans;
return 0;
}
2025/11/15
A. 诅咒
糖丸了真是。有一个显而易见的贪心是选择最小的数然后乘 \(k\)。等到最小的数乘 \(k\) 大于最大的数的时候就可以把剩余的次数均摊到每一个数字身上了。打比赛的时候没想明白这个东西是对的,害得我又想了好久其他的东西
Code
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
using i28 = __int128_t;
const int kN = 2e5 + 1, kM = 1e9 + 7;
int n, m, k, fl, ans, v[kN];
multiset<i28> st;
int Pow(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = 1ll * x * x % kM)
(y & 1) && (res = 1ll * res * x % kM);
return res;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m >> k;
for (int i = 1, x; i <= n; i++)
cin >> x, st.insert(x);
if (n == 1) {
cout << int(*st.begin() * Pow(k, m) % kM);
return 0;
}
if (*st.begin() == 0 || k == 1) {
for (auto it : st)
ans = (ans + it) % kM;
cout << ans;
return 0;
}
for (; *st.begin() * k <= *st.rbegin() && m; m--) {
i28 t = *st.begin();
st.erase(st.begin());
st.insert(t * k);
}
int cnt = 0;
for (auto it : st) {
cnt++;
// cerr << (int)it << ' ';
ans = (ans + it % kM * Pow(k, m / n + (cnt <= m % n)) % kM) % kM;
}
cout << ans;
return 0;
}
B. 食物
dp 状态是显然的,设 \(f_{i, j}\) 表以 \(i\) 为起点,最多走 \(j\) 步能获得的最大权值。考虑 dp 转移,首先全部向左和全部向右是简单的,前缀和即可。考虑怎么处理往左走再拐(往右走再拐同理)的方案。注意到相当于 \(f_{i - 1, j}\),因为它包含了可能为最大区间的答案,并且恰好多走一步。滚动数组优化空间即可
Code
#include <iostream>
using namespace std;
using ll = long long;
const int kN = 8001;
int n;
ll ans, s[kN], fl[kN], fr[kN], res[kN];
int main() {
#ifndef ONLINE_JUDGE
freopen("in", "r", stdin);
freopen("out", "w", stdout);
#endif
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++)
cin >> s[i], s[i] += s[i - 1];
auto Calc = [](int l, int r) { return s[r] - s[l - 1]; };
for (int j = 1; j <= 2 * n; j++) {
for (int i = n; i >= 1; i--)
fr[i] = max(fr[i - 1], Calc(i, min(n, i + j)));
for (int i = 1; i <= n; i++)
fl[i] = max(fl[i + 1], Calc(max(1, i - j), i));
for (int i = 1; i <= n; i++)
res[i] ^= j * max(fl[i], fr[i]);
}
for (int i = 1; i <= n; i++)
ans ^= i + res[i];
cout << ans;
return 0;
}

浙公网安备 33010602011771号