做题记录(2026.1.5~2026.1.11)
感觉我太鸽子了,懒得写题解但是感觉又不能不写,所以写点简略的题解,好题会单独写一篇题解挂上来。
CF2068E Porto Vs. Benfica
题意:给出一个图,有一个人从 \(1\to n\),你可以随时 ban 掉一条边。假设你和对方都采取最优策略,求问这个人的路径长度最长是多少。
做法:显然 by wzy /qiang
考虑该怎么 ban 这条边,肯定是这个人先按照最短路径走,然后我在他要走的下一步给 ban 掉。先跑出来最短路树,记 \(f(u)\) 代表在点时,还没 ban 的答案是多少,\(g(u)\) 代表我 ban 掉 \(u\to pre_u\) 的代价是多少。
考虑 \(g\) 怎么求,那么就是我跑到子树内再通过一条非树边出去,再走最短路。这个可以拆贡献,然后按代价排序用并查集覆盖去算。
考虑 \(f\),\(f(u)=\max(g(u),\min{f(v)+1})\),注意到最小的 \(f\) 一定不可能再被更新,直接按这个东西跑类似 dij 即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 4e5 + 5;
int n, m, pre[maxn], dis[maxn];
vector<int> e[maxn];
struct node {
int p, val;
friend bool operator<(node x, node y) {
return x.val > y.val;
}
} ;
int vis[maxn];
void dijkstra(int s) {
priority_queue<node> q;
q.push(node{s, 0});
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
dis[s] = 0;
while(!q.empty()) {
int u = q.top().p; q.pop();
if(vis[u])
continue;
vis[u] = 1;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(dis[v] > dis[u] + 1) {
dis[v] = dis[u] + 1;
q.push(node{v, dis[v]});
pre[v] = u;
}
}
}
}
int val[maxn], ans[maxn];
vector<int> g[maxn];
struct Edge {
int x, y, v;
friend bool operator<(Edge x, Edge y) {
return x.v < y.v;
}
} E[maxn];
int tot;
int pre1[maxn];
void prepare() {
for (int i = 1; i <= n; i++)
for (int j = 0; j < e[i].size(); j++)
if(pre[i] != e[i][j] && pre[e[i][j]] != i)
E[++tot] = {i, e[i][j], dis[i] + dis[e[i][j]] + 1};
for(int i = 1; i <= n; i++)
pre1[i] = i;
}
int fnd(int x) {
return pre1[x] == x ? x : fnd(pre1[x]);
}
void solve() {
for (int i = 1; i <= n; i++)
val[i] = 2e9;
val[n] = 0;
sort(E + 1, E + tot + 1);
for (int i = 1; i <= tot; i++) {
int x = E[i].x, y = E[i].y;
x = fnd(x), y = fnd(y);
while(x != y) {
if(dis[x] < dis[y])
swap(x, y);
val[x] = E[i].v - dis[x];
//cout << E[i].x << " " << E[i].y << " " << x << endl;
pre1[x] = fnd(pre[x]);
x = fnd(x);
}
}
// for (int i = 1; i <= n; i++)
// cout << val[i] << " ";
// cout << endl;
}
void dijkstra2(int s) {
priority_queue<node> q;
memset(vis, 0, sizeof(vis));
memset(ans, 0x3f, sizeof(ans));
ans[s] = 0;
q.push(node{s, ans[s]});
while(!q.empty()) {
int u = q.top().p; q.pop();
if(vis[u])
continue;
vis[u] = 1;
for (int i = 0; i < e[u].size(); i++) {
int v = e[u][i];
if(ans[v] > max(ans[u] + 1, val[v])) {
ans[v] = max(ans[u] + 1, val[v]);
q.push(node{v, ans[v]});
pre[v] = u;
}
}
}
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y; cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
dijkstra(n);
for (int i = 1; i <= n; i++)
g[pre[i]].push_back(i);
// dfs(n);
prepare(), solve();
dijkstra2(n);
cout << (ans[1] >= 2e9 ? -1 : ans[1]) << endl;
return 0;
}
CF2077E Another Folding Strip
题意:给出一个长为 \(n\) 的序列 \(a\),定义对于一个长为 \(m\) 的序列 \(b\),\(f(b)\) 代表,考虑一条 \(1\times m\) 的纸条,你每次可以在两个单元格中间的直线位置随意折叠,然后选择在一个单元格滴一滴墨水,墨水会穿透下面的所有单元格。操作完后纸条重新展开。每个单元格 \(i\) 需要透过墨水 \(b_i\) 次。\(f(b)\) 即完成这个任务所需要的最少操作次数。
现在求问 \(\sum\limits_{l=1}^n\sum\limits_{r=l}^nf(a[l,r])\)。
做法:
首先观察这个操作的性质,发现是等同于选择一个序列 \(p_1,p_2,\cdots p_k\),要求相邻两个奇偶不同即可,因为我们可以从左往右折叠去弄出来我们想要的位置。
然后还得发掘性质,先考虑一个序列,经过手玩若干次后发现答案是 \(\max\limits_{l,r} \sum\limits_{i=l}^r a_i(-1)^{i-l}\)。考虑证明。
我们发现,如果我们令 \(u<v,u\not \equiv v\pmod 2\) 间连一条 \(u\to v\) 的有向边,这个东西有点类似于一个 dag 最小不可交覆盖,所以我们考虑用 flow 去刻画。
我们左侧 \(n\) 个点,右侧 \(n\) 个点,分别代表一个点的出度和入度,然后按照上面这个图的边进行两部点之间的连边,那么答案就是 \(\sum a - \text{max match}\)。然后因为这是个二分图,所以我们考虑上 hall 定理,即 \(\text{max match} = |L|-\max\limits_{S\in L} {|S|-|N(S)|}\),带回到柿子就是 \(\max_{S\in L}|S|-|N(S)|\)。
注意到这个图很有性质,\(u\) 的邻域是 \(u+2\) 的超集,所以我们考虑奇数和偶数分别的最小值 \(x, y\),比他们大的同奇偶的数一定被选入,那么我们就是算一下这个代价是 \(\sum a_i(-1)^{i-x}+\sum a_i(-1)^{i-y}\),稍微整理一下就是上面那个柿子。
如果是偶数的最大值减去奇数的最小值这种问题有点麻烦,但是我们发现偶数的最大值减去偶数的最小值这样的方式一定不是很牛,所以记 \(b_i = \sum\limits_{j=1}^i (a_j)(-1)^j\),那么 \(f(a[l,r]) = \max\limits_{i=l-1}^rb_i-\min\limits_{i=l-1}^rb_i\)。用单调栈瞎维护一下即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5 + 5, mod = 998244353;
int b[maxn], a[maxn], n, l[maxn], r[maxn], st[maxn], top;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i], b[i] = b[i - 1] + (i & 1 ? 1 : -1) * a[i];
for (int i = 0; i <= n; i++) {
while(top && b[st[top]] < b[i])
r[st[top--]] = i;
st[++top] = i;
}
while(top)
r[st[top--]] = n + 1;
for (int i = n; i >= 0; i--) {
while(top && b[st[top]] <= b[i])
l[st[top--]] = i;
st[++top] = i;
}
while(top)
l[st[top--]] = -1;
int ans = 0;
for (int i = 0; i <= n; i++)
ans = (ans + ((r[i] - i) * (i - l[i]) - 1) % mod * (b[i] % mod) % mod + mod) % mod;
// cout << ans << endl;
for (int i = 0; i <= n; i++) {
while(top && b[st[top]] > b[i])
r[st[top--]] = i;
st[++top] = i;
}
while(top)
r[st[top--]] = n + 1;
for (int i = n; i >= 0; i--) {
while(top && b[st[top]] >= b[i])
l[st[top--]] = i;
st[++top] = i;
}
while(top)
l[st[top--]] = -1;
for (int i = 0; i <= n; i++)
ans = (ans - ((r[i] - i) * (i - l[i]) - 1) % mod * (b[i] % mod) % mod + mod) % mod;
cout << ans << endl;
}
signed main() {
int T; cin >> T;
while(T--)
solve();
return 0;
}
AT_agc076_b [AGC076B] Split and Reverse
https://www.cnblogs.com/LUlululu1616/p/19444906
AT_agc076_c [AGC076C] Slime Eat Slime
https://www.cnblogs.com/LUlululu1616/p/19446883
CF1286F Harry The Potter
https://www.cnblogs.com/LUlululu1616/p/19449687
qoj12334 Tritwise Mex
题意:给出两个序列 \(a,b\)。定义 \(\operatorname{mex}_3(x, y) = \sum\limits _{i=0}^{k-1} \operatorname{mex} (x_i,y_i)3^i\),这里 \(x_i,y_i\) 是 \(x,y\) 三进制下第 \(i\) 的值。
求序列 \(c\),其值为 \(c_i = \sum\limits_{\operatorname{mex}_3(x,y) = i} a_xb_y\)。序列长 \(n=3^k,k\le 12\)。
做法:
对于这种神秘类多项式乘法,因为他是每一位单独计算,所以我们考虑从高位到低位进行处理。
那么假设我们这一位上分别有多项式 \((a_0,a_1,a_2),(b_0,b_1,b_2)\),这里 \(a_0\) 代表 \(a\) 中这一位是 \(0\) 的那些元素,其他的类似,我们希望最后得到 \((c_0,c_1,c_2)\),我们可以暴力枚举 \(i,j\) 然后让 \(a_ib_j\) 贡献到 \(c_{\operatorname{mex}_3(i,j)}\) 上,但是这样是 \(9^k\) 太不牛了,考虑优化一下。
我们注意到,\((a_0,a_1,a_2)\times (b_0,b_1,b_2)=((a_1+a_2)(b_1+b_2),(a_0+a_1+a_2)(b_0+b_1+b_2)-(a_1+a_2)(b_1+b_2) - a_0b_1-a_1b_0,a_0b_1+a_1b_0)\),观察到这个柿子只需要四次乘法,复杂度变成 \(T(k) = 3^k+4T(k-1) = 4^k\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 6e5 + 5;
int n;
struct Poly {
vector<int> a;
int size() {
return a.size();
}
void resize(int N) {
a.resize(N);
}
int& operator[](int x) {
return a[x];
}
friend Poly operator+(Poly f, Poly g) {
for (int i = 0; i < f.size(); i++)
f[i] = (f[i] + g[i]);
return f;
}
friend Poly operator-(Poly f, Poly g) {
for (int i = 0; i < f.size(); i--)
f[i] = (f[i] - g[i]);
return f;
}
} ;
int pw[maxn];
void cal_mul(Poly a, Poly b, Poly &c, int n) {
if(n == 1) {
c.resize(3);
c[0] = (a[2] + a[1]) * (b[2] + b[1]);
// cout << a[1] << " " << a[0] << endl;
c[2] = a[0] * b[1] + a[1] * b[0];
c[1] = (a[0] + a[1] + a[2]) * (b[0] + b[1] + b[2]) - c[0] - c[2];
return ;
}
Poly ta[4], tb[4], tc[4];
for (int i = 0; i < 4; i++)
ta[i].resize(pw[n - 1]), tb[i].resize(pw[n - 1]), tc[i].resize(pw[n - 1]);
for (int i = 0; i < pw[n - 1]; i++)
ta[0][i] = (a[i + pw[n - 1] * 2] + a[i + pw[n - 1]]),
ta[1][i] = a[i],
ta[2][i] = a[i + pw[n - 1]],
ta[3][i] = (a[i] + a[i + pw[n - 1]] + a[i + pw[n - 1] * 2]);
for (int i = 0; i < pw[n - 1]; i++)
tb[0][i] = (b[i + pw[n - 1] * 2] + b[i + pw[n - 1]]),
tb[1][i] = b[i],
tb[2][i] = b[i + pw[n - 1]],
tb[3][i] = (b[i] + b[i + pw[n - 1]] + b[i + pw[n - 1] * 2]);
cal_mul(ta[0], tb[0], tc[0], n - 1);
cal_mul(ta[1], tb[2], tc[1], n - 1);
cal_mul(ta[2], tb[1], tc[2], n - 1);
cal_mul(ta[3], tb[3], tc[3], n - 1);
c.resize(pw[n]);
for (int i = 0; i < pw[n - 1]; i++) {
c[i] = tc[0][i];
c[i + 2 * pw[n - 1]] = tc[1][i] + tc[2][i];
c[i + 1 * pw[n - 1]] = tc[3][i] - c[i] - c[i + 2 * pw[n - 1]];
// cout << tc[3][i] << " " << ta[3][i] << " " << tb[3][i] << endl;
}
}
signed main() {
cin >> n;
pw[0] = 1;
for (int i = 1; i <= n; i++)
pw[i] = pw[i - 1] * 3;
Poly a, b, c;
a.resize(pw[n]), b.resize(pw[n]), c.resize(pw[n]);
for (int i = 0; i < pw[n]; i++)
cin >> a[i];
for (int i = 0; i < pw[n]; i++)
cin >> b[i];
cal_mul(a, b, c, n);
for (int i = 0; i < pw[n]; i++)
cout << c[i] << " ";
cout << endl;
return 0;
}
qoj9562 欧伊昔
题意:和上个题类似,只不过运算不一定是 \(\operatorname{mex}\),而是给了一个映射 \(\operatorname{op}\),保证这个映射除了某些 subtask 以外随机生成,\(n\le 11\)。
做法:
考虑上面那个做法在做啥,就是我把一些能一起算的东西一起算掉,如果我对于一个位用了 \(x\) 次操作那么复杂度就是 \(O(x^n)\),我们考虑怎么样对于任意的映射都能找到一个比较好的方式。
首先我们可以把出现次数最多的用整体减去部分直接干掉,花费一次操作。我们发现最多次数的至少为 \(3\),就意味着剩下的位置至多只有 \(6\) 个,我们只需要压掉一次操作就可以做到 \(O(6^n)\) 可以通过此题!注意到如果是一个拉丁方的形状那么就找不到映射中一行或者一列存在两个相同的元素,如果找得到那么把他们一起问掉就可以节省一次。但是对于拉丁方,也就是本题中 subtask1 里的 \(\operatorname{op}(i,j)=(i+j)\bmod 3\),我们可以对拉丁方通过一定的映射后变成 sub1 的限制,用 \(3\) 进制 fwt 解决,这样就不需要依赖于映射随机了,复杂度 \(O(6^n)\)。
代码就咕咕咕了,不会写 \(3\) 进制 fwt,写起来太麻烦了就不写了。

浙公网安备 33010602011771号