26.7~26.8 盛夏
26.7.1 vp 2022 SHCPC solved 7 Penalty 824
26.7.8 vp 2025 SHCPC solved 5 Penalty 517
26.7.26 2026 SHCPC solved 4 Penalty 604 rank 63 Cu
备战上海市赛。感觉以往年经验来看,银和金的跨度过于大了,再加之高等势力往往挑上海站打,所以也不抱什么打金希望了,希望能保持一个高位银吧 /kel。24 和 25 队友都亲身参与过,或许后两天自己挑着做做。做 22 年这一套有一种图论 cpc 的感觉,但 这件事好久没做了,优势区间软脚这块。
2022 SHCPC inv B.BracketQuery
邻接表该滚出去了,TLE 我还以为算假了,补题换 vector 一遍过。
依旧套路的左括号视作 \(+1\),右括号视作 \(-1\),转化到前缀和上,考虑如何刻画限制。对于合法的括号序列,需要满足:
- \(\forall i \in [1,n], |s_i - s_{i - 1}| = 1\)
- \(\forall i \in [1,n], s_i \geq 0\)
- \(s_n = 0\)
对于给定的条件,我们可以描述为 \(s_r - s_{l - 1} = c_i\)。我们将第一条限制放宽为 \(-1 \leq s_i - s_{i - 1} \leq 1\),对上述所有限制建图直接在前缀和上跑差分约束。\(s_i = s_{i - 1}\) 和 \(s_i < 0\) 的情况可以在跑完之后额外判断,存在负环说明条件矛盾无解。最后再差分回去即可得出构造的括号序列。复杂度 \(\mathcal O(nq)\),能过神秘。
点击查看代码
#include<bits/stdc++.h>
#define eb emplace_back
#define mp make_pair
using namespace std;
static const int MAX = 3010;
static const int mod = 1e9 + 7;
int n, m, x, y, z;
vector<pair<int, int> > G[3010];
int dis[MAX], vis[MAX], tim[MAX];
queue<int> q;
inline bool spfa(int st)
{
q.emplace(st);
for(int i = 1; i <= n; ++i) vis[i] = tim[i] = 0, dis[i] = INT_MAX;
dis[st] = 0, vis[st] = 1;
while(!q.empty())
{
int now = q.front(); q.pop();
vis[now] = 0;
for(auto [to, val]:G[now])
{
if(dis[to] > dis[now] + val)
{
dis[to] = dis[now] + val, tim[to] = tim[now] + 1;
if(tim[to] == n) return 0;
if(!vis[to]) q.emplace(to), vis[to] = 1;
}
}
}
for(int i = 1; i < n; ++i) if(dis[i] < 0 || dis[i] == dis[i + 1]) return 0;
return dis[n] == 0;
}
inline void solve()
{
cin >> n >> m, ++n;
for(int i = 1; i < n; ++i) G[i].eb(mp(i + 1, 1)), G[i + 1].eb(mp(i, 1));
G[1].eb(mp(n, 0)), G[n].eb(mp(1, 0));
for(int i = 1; i <= m; ++i) cin >> x >> y >> z, G[x].eb(mp(y + 1, z)), G[y + 1].eb(mp(x, -z));
if(spfa(1)) {
cout << "! ";
for(int i = 2; i <= n; ++i)
{
if(dis[i] - dis[i - 1] == 1) cout << "(";
if(dis[i] - dis[i - 1] == -1) cout << ")";
}
} else {
cout << "?";
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
2022 SHCPC inv I.It Takes Two of Two
为啥算错了/qd。把合作关系视为加边,合法的图中只存在若干链、环和孤立点。我们把长度为 \(1\) 的链称为短链,长度大于 \(1\) 的链称为长链。显然对于长链,加边只能加在端点处。并有以下分类讨论:
- 当边加在两个孤立点之间时,生成一个短链;
- 当边加在孤立点和短链之间时,生成一个长链;
- 当边加在孤立点和长链之间时,生成一个长链;
- 当边加在两个短链之间时,生成一个长链;
- 当边加在短链和长链之间时,生成一个长链;
- 当边加在长链和长链之间时,生成一个长链;
- 当边加在一个长链的两端时,生成一个环;
采取记忆化搜索,记录孤立点、短链、长链的数量即可做到 \(\mathcal O(n^3)\)。
点击查看代码
#include<bits/stdc++.h>
#define eb emplace_back
#define mp make_pair
using namespace std;
static const int MAX = 210;
static const int eps = 1e-10;
int n, v[MAX][MAX][MAX];
double f[MAX][MAX][MAX];
double dfs(int c0, int c1, int c2)
{
if(c0 < 0 || c1 < 0 || c2 < 0) return 0;
if(v[c0][c1][c2]) return f[c0][c1][c2];
double &res = f[c0][c1][c2];
v[c0][c1][c2] = 1;
int a = c0 * (c0 - 1); //孤立点和孤立点连成1链
int b = c0 * c1 * 4; //孤立点和1链连成长链
int c = c0 * c2 * 4; //孤立点和长链连成长链
int d = c1 * (c1 - 1) * 4; //1链和1链连成长链
int e = c1 * c2 * 8; //1链和长链连成长链
int g = c2 * (c2 - 1) * 4; //长链和长链连成长链
int h = c2 * 2; //长链自己连成环
int t = n * n;
if(a + b + c + d + e + g + h == 0) return res;
return res = (t +
a * dfs(c0 - 2, c1 + 1, c2) +
b * dfs(c0 - 1, c1 - 1, c2 + 1) +
c * dfs(c0 - 1, c1, c2) +
d * dfs(c0, c1 - 2, c2 + 1) +
e * dfs(c0, c1 - 1, c2) +
g * dfs(c0, c1, c2 - 1) +
h * dfs(c0, c1, c2 - 1)
) / (double)(a + b + c + d + e + g + h);
}
inline void solve()
{
cin >> n;
cout << fixed << setprecision(9) << dfs(n, 0, 0);
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
2022 SHCPC inv J. Just Some Bad Memory
很好理解 dfs 树的集大成题目。给定一个图,判断最少加多少条边可以让它既有奇环又有偶环。首先是判定,判奇环可以二分图染色,判偶环可以用 dfs 树打标记,并同时记录环长度。一个图没有偶环时,每条边属于至多一个环,也即是仙人掌。所有的奇环都没有公共边,否则通过删掉公共边一定能得到一个偶环。进行一些分讨:
- 首先 \(n < 4\) 一定无解。
- 如果既有奇环又有偶环,答案是 \(0\),否则至少是 \(1\)。
- 如果有偶环,那么只需要在上面构造奇环即可,答案为 \(1\)。
- 如果有大小 \(> 3\) 的奇环,或者大小等于 \(3\) 的奇环额外连出去一条两个点的链(即一条四个点的链,长得像乙基环丙烷),直接在上面构造偶环即可,答案也为 \(1\)。
- 否则答案至少为 \(2\),此时要么是森林没环,要么是只有大小为 \(3\) 的环且没有四个点的链。
-如果有大小为 \(3\) 的环,再连出两条边就可以得到一个大小为 \(4\) 的环,答案为 \(2\)。- 如果有长度 \(> 3\) 的链,可以加一条边得到奇环,加一条边得到偶环,答案也为 \(2\)。
- 否则所有链长度不超过 \(3\)。注意到我们最多只需要加 \(5\) 条边,仅需利用原图的至多 \(2\) 条边,而这 \(2\) 条边关系如何是任意的,因此最后情况的答案为 \(5 - \min (m, 2)\)。
找长度为 \(4\) 的链可以枚举中间边,再枚举延伸出去的点。判断一个点属于几个环我们可以用树上差分实现,这一步可以和二分图染色以及计算环的长度在一个 dfs 中完成。复杂度 \(\mathcal O(n + m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define fi first
#define se second
#define pii pair<int, int>
#define mp make_pair
using namespace std;
static const int MAX = 100010;
static const int mod = 998244353;
int n, m, x, y, siz, flag;
int f1, f2, f3,f4;
vector<int> G[MAX];
vector<pii> E;
int dep[MAX], col[MAX], sum[MAX];
void dfs(int now, int father)
{
dep[now] = dep[father] + 1, ++siz;
for(auto to:G[now]) if(to != father)
{
if(!col[to]) {col[to] = 3 - col[now], dfs(to, now), sum[now] += sum[to]; continue;}
if(dep[now] < dep[to]) continue;
if((dep[now] ^ dep[to]) & 1) f1 = 1;
flag |= col[now] == col[to], --sum[to], ++sum[now];
}
if(sum[now] > 1) f1 = 1;
}
bool check(int x, int y)
{
for(int i = 0; i < G[x].size() && i < 3; ++i) for(int j = 0; j < G[y].size() && j < 3; ++j) if(G[x][i] != G[y][j] && G[x][i] != y && G[y][j] != x) return 1;
return 0;
}
inline void solve()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i) cin >> x >> y, G[x].eb(y), G[y].eb(x), E.eb(mp(x, y));
if(n < 4) return cout << "-1" << '\n', void();
for(int i = 1; i <= n; ++i) if(!col[i])
{
siz = flag = 0, col[i] = 1, dfs(i, 0);
if(flag) f2 = max(f2, siz);
f3 = max(f3, siz);
}
for(auto [x, y]:E) if(check(x, y)) {f4 = 1; break;}
if(f1 && f2) return cout << "0" << '\n', void();
if(f1 || f2 > 3 || f2 && f4) return cout << "1" << '\n', void();
if(f2 || f3 > 3) return cout << "2" << '\n', void();
cout << 5 - min(m, 2ll) << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
实名打不过去年 shnu 一队。感觉单挑最大的问题在于后半程甚至中半程直接开摆,过题全集中在前面了,后面脑子转不动且光被骗,玉玉了。
2025 SHCPC inv J. 画圈
题解神了。题解给了一个连通性无关。炒一下题解(但其实我没大看懂):把操作看成两种情况:(1)把一个任意白边变黑,贡献 \(+0\);(2)把一个两端被黑边连通的白边变黑,贡献 \(+1\)。按照先黑边后白边的顺序并查集维护即可,时间复杂度 \(\mathcal O(m \alpha(n))\)。
但我只会连通性相关。首先度数为 \(1\) 的点不可能在环上,无效点可以先全部删掉。然后对剩下的点连黑边,对只有黑边的图做缩点,接着再在新图上连白边(包括自环)。最终形态是若干个连通块,每个连通块都是一个环加若干个耳。现在要做的是删多少条边可以是原图变成森林,众所周知 \(n\) 个点的树有 \(n - 1\) 条边,用并查集统计连通块数量即可。复杂度同上,但是多做了 tarjan 常数大。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define fi first
#define se second
#define pii pair<int, int>
#define mp make_pair
using namespace std;
static const int MAX = 200010;
static const int mod = 998244353;
int n, m, x, y, z, ans, tag[MAX], deg[MAX];
vector<pii> E[2];
vector<int> G[MAX], g[MAX];
queue<int> q;
int vis[MAX], stk[MAX], top;
int dfn[MAX], low[MAX], tot;
int col[MAX], num;
void tarjan(int now)
{
dfn[now] = low[now] = ++tot, stk[++top] = now, vis[now] = 1;
for(auto to:G[now]) if(!dfn[to]) tarjan(to), low[now] = min(low[now], low[to]); else if(vis[to]) low[now] = min(low[now], dfn[to]);
if(low[now] == dfn[now])
{
col[now] = ++num, vis[now] = 0;
while(stk[top]!=now) col[stk[top]]=num, vis[stk[top]]=0, --top;
--top;
}
}
int fa[MAX], siz[MAX], con;
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
inline void merge(int x, int y)
{
x = find(x), y = find(y);
if(x == y) return;
if(siz[x] > siz[y]) swap(x, y);
fa[x] = y, siz[y] += siz[x];
}
inline void solve()
{
cin >> n >> m, top = num = tot = ans = con = 0;
E[0].clear(), E[1].clear();
for(int i = 1; i <= n; ++i) col[i] = tag[i] = deg[i] = dfn[i] = low[i] = vis[i] = 0, G[i].clear(), g[i].clear();
for(int i = 1; i <= m; ++i) cin >> x >> y >> z, E[z].eb(mp(x, y)), g[x].eb(y), g[y].eb(x), ++deg[x], ++deg[y];
for(int i = 1; i <= n; ++i) if(deg[i] == 1) q.emplace(i);
while(!q.empty())
{
int now = q.front(); q.pop();
tag[now] = 1;
for(auto to:g[now]) if(!tag[to])
{
if(--deg[to] == 1) q.emplace(to);
}
}
for(auto [x, y]:E[1]) if(!tag[x] && !tag[y]) G[x].eb(y), G[y].eb(x);
for(int i = 1; i <= n; ++i) if(!col[i]) tarjan(i);
for(int i = 1; i <= num; ++i) fa[i] = i, siz[i] = 1;
for(int now = 1; now <= n; ++now) for(auto to:G[now]) if(col[now] != col[to]) ++ans, merge(col[now], col[to]);
ans /= 2;
for(auto [x, y]:E[0]) if(!tag[x] && !tag[y]) ++ans, merge(col[x], col[y]);
for(int i = 1; i <= num; ++i) con += i == find(i);
cout << ans - (num - con) << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
2025 SHCPC inv E. Djangle 的数据结构
好久没写数据结构了。维护推平标记、区间和、区间 lcm。对于推平操作直接正常线段树写即可。对于询问操作,拆成先取 gcd 再求区间和。区间取 gcd 是经典势能分析题目,对于一个区间的 lcm 如果是 x 的因子的话,那么操作肯定不会造成影响;否则直接暴力递归下去修改即可。需要特别注意的是如果区间上存在推平标记,需要直接区间推平成标记和 x 的 gcd。有一个问题在于存 lcm 的时候可能爆龙龙,注意到 \(\forall x < 2^{30}\),当区间 lcm 超过 \(2^{30}\) 的时候可以额外标记,因为此时一定会进行暴力修改操作。
定义总势能为线段树全相等节点的 \(\log V\) 总和。每次推平操作至多修改 \(\log n\) 个节点,势能至多增加 \(\log n \log V\)。对于求 gcd 操作,每次最多拆开两个两侧全相等的节点,相当于两次区间推平操作。对于中间部分每遍历一个区间势能至少减少 \(1\),暴力做就是 \(\mathcal O(\log n \log V)\) 次操作。至此总复杂度 \(\mathcal O(m \log n \log^2 V)\),主要在于推平操作使势能增加,有一个求 gcd 的 \(\log V\) 在里面。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
static const int MAX = 100010;
static const int lim = 1 << 30;
int n, m, a[MAX], op, x, y, z;
int Lcm[MAX << 2], cov[MAX << 2], sum[MAX << 2];
inline void pushup(int i)
{
if((!~Lcm[i << 1]) || (!~Lcm[i << 1 | 1])) Lcm[i] = -1; else Lcm[i] = Lcm[i << 1] * Lcm[i << 1 | 1] / __gcd(Lcm[i << 1], Lcm[i << 1 | 1]), Lcm[i] = Lcm[i] > lim ? -1 : Lcm[i];
sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
inline void tag(int i, int k, int l, int r) {cov[i] = Lcm[i] = k, sum[i] = (r - l + 1) * k;}
inline void pushdown(int i, int l, int r)
{
if(!cov[i]) return;
int mid = (l + r) >> 1;
tag(i << 1, cov[i], l, mid), tag(i << 1 | 1, cov[i], mid + 1, r);
cov[i] = 0;
}
void build(int i, int l, int r)
{
cov[i] = 0;
if(l == r) return Lcm[i] = sum[i] = a[l], void();
int mid = (l + r) >> 1;
build(i << 1, l, mid), build(i << 1 | 1, mid + 1, r);
pushup(i);
}
void cover(int i, int l, int r, int L, int R, int k)
{
if(l <= L && R <= r) return tag(i, k, L, R);
pushdown(i, L, R);
int mid = (L + R) >> 1;
if(l <= mid) cover(i << 1, l, r, L, mid, k);
if(r > mid) cover(i << 1 | 1, l, r, mid + 1, R, k);
pushup(i);
}
inline void cover(int l, int r, int k) {cover(1, l, r, 1 ,n, k);}
void modify(int i, int l, int r, int L, int R, int k)
{
if(Lcm[i] > 0 && k % Lcm[i] == 0) return;
if(cov[i]) return cover(i, max(l, L), min(r, R), L, R, __gcd(k, cov[i]));
pushdown(i, L, R);
if(L == R) return Lcm[i] = sum[i] = __gcd(sum[i], k), cov[i] = 0, void();
int mid = (L + R) >> 1;
if(l <= mid) modify(i << 1, l, r, L, mid, k);
if(r > mid) modify(i << 1 | 1, l, r, mid + 1, R, k);
pushup(i);
}
inline void modify(int l, int r, int k) {modify(1, l, r, 1, n, k);}
int query(int i, int l, int r, int L, int R)
{
if(l <= L && R <= r) return sum[i];
pushdown(i, L, R);
int mid = (L + R) >> 1, res = 0;
if(l <= mid) res += query(i << 1, l, r, L, mid);
if(r > mid) res += query(i << 1 | 1, l, r, mid + 1, R);
return res;
}
inline int query(int l, int r) {return query(1, l, r, 1, n);}
inline void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
build(1, 1, n);
for(int i = 1; i <= m; ++i)
{
cin >> op >> x >> y >> z;
if(op == 0) cover(x, y, z);
if(op == 1) modify(x, y, z), cout << query(x, y) << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
嫩了啊,台风来了,台风软脚了。已严肃取消上海市赛参赛计划,小王和 wy 双人参赛了。希望嫖个牌子。
又被征召了,即将上海两日游。有人邀请我住到祂家里,是不是有点暧昧了。然后是严肃开始牛客和杭电,一周四打可能是似了。
2026 杭电夏季 D2 J. 另一个 shu 论问题
给定一棵树,求 \(lca(x, y) = \gcd(x, y)\) 的对数。条件等价于 \(lca(x, y) | x, lca(x, y) | y, \gcd(x / lca(x, y), y / lca(x, y)) = 1\)。做 dfs 遍历到某一个节点 \(u\) 时,我们需要得到 \(u\) 的所有因子,以及根到它的路径上经过了哪些点。若 \(u\) 的一个因子 \(d\) 在根到 \(u\) 的路径上,\(d\) 就可能成为 \(u\) 的 lca 的一个答案。
记 \(f_d\) 表示满足 \(\gcd(x, y) = d\) 的点对数量,\(g_d\) 表示满足 \(d | \gcd(x, y)\) 的点对数量,则对于一个点 \(u\) 有 \(g_u = \sum_{u | d} f_d\),莫反一下有 \(f_u = \sum_{u | d} g_d \mu(\frac{d}{u})\)。做一个 dsu on tree,维护子树里面有多少个 \(d\) 的倍数 \(C_d\),每次加入新的点 \(v\) 时,枚举 \(v\) 的所有因子 \(d\),此时 \(g_u\) 增加了 \(C_d\),答案增加了 \(C_d \mu(\frac{d}{u})\)。
\(1 \sim n\) 的所有因子和是个调和级数的复杂度,因为每个因子都要做合并操作,所以相当于对 \(\mathcal O(n \log n)\) 个点做 dsu on tree,总复杂度为 \(\mathcal O(n \log^2 n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 2e5;
int n, x, y, ans, cur;
int comp[N + 10], P[N + 10], mu[N + 10], Pcnt;
int vis[N + 10], dep[N + 10], p[N + 10], stamp[N + 10], cnt[N + 10];
vector<int> fac[N + 10], G[N + 10], used;
vector<pii> cand[N + 10];
inline void Sieve()
{
mu[1] = 1;
for(int i = 2; i <= N; ++i)
{
if(!comp[i]) P[++Pcnt] = i, mu[i] = -1;
for(int j = 1; j <= Pcnt; ++j)
{
if(i * P[j] > N) break;
comp[i * P[j]] = 1;
if(i % P[j] == 0) {mu[i * P[j]] = 0; break;} else mu[i * P[j]] = -mu[i];
}
}
for(int d = 1; d <= N; ++d) for(int x = d; x <= N; x += d) fac[x].eb(d);
}
int calc(const vector<pii> &vec, int l, int r)
{
int len = r - l, res = 0;
if(len < 2) return 0;
++cur, used.clear();
for(int i = l; i < r; ++i)
{
int a = vec[i].fi;
for(auto d:fac[a])
if(stamp[d] != cur)
{
stamp[d] = cur;
if(mu[d] != 0) cnt[d] = 1, used.eb(d);
}
else if(mu[d] != 0) ++cnt[d];
}
for(auto d:used) res += mu[d] * cnt[d] * (cnt[d] - 1) / 2;
return res;
}
void dfs(int now, int father)
{
dep[now] = dep[father] + 1, p[dep[now]] = now, vis[now] = 1;
for(auto f:fac[now]) if(vis[f])
{
int flag;
if(f == now) flag = 0; else flag = p[dep[f] + 1];
cand[f].eb(now / f, flag);
}
for(auto to:G[now]) if(to != father) dfs(to, now);
vis[now] = 0;
}
inline void solve()
{
cin >> n, ans = 0;
for(int i = 1; i <= n; ++i) G[i].clear(), cand[i].clear(), stamp[i] = cnt[i] = p[i] = dep[i] = vis[i] = 0;
for(int i = 1; i < n; ++i) cin >> x >> y, G[x].eb(y), G[y].eb(x);
dfs(1, 0);
for(int f = 1; f <= n; ++f)
{
auto &vec = cand[f];
int siz = (int)vec.size();
if(siz < 2) continue;
int res = calc(vec, 0, siz);
sort(vec.begin(), vec.end(), [](const auto &a, const auto &b){return a.se != b.se ? a.se < b.se : a.fi < b.fi;});
for(int i = 0; i < siz;)
{
int j = i + 1;
while(j < siz && vec[j].se == vec[i].se) ++j;
if(j - i >= 2) res -= calc(vec, i, j);
i = j;
}
ans += res;
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
Sieve();
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
重回上海,打出历史最差了。没有任何借口,这次我太坑了,还有赎罪机会吗?真是挨打要立正,打完比赛整个人都是麻的,我真的觉得自己蠢到家了,能写出这样一版浪费时间的弱智东西,对不起所有人啊。
2026 SHCPC inv D. 收集符文
牛大了的诈骗。首先需要注意到的是操作顺序不会影响操作结果,所以我们可以任意选定一个格子开始我们的操作。实际上反复操作不合法的格子直到整张网格都合法,得到的就是最小操作次数。假设说当前选定要操作的格子里值为 \(b_u\),目标值为 \(a_u\),相邻了 \(d_u\) 个格子,那么我们每次对这个格子操作 \(k = \lceil \frac{a_u - b_u}{d_u + 1} \rceil\) 次,当前格子值变为 \(b_u + k \times (d_u + 1)\),四周的格子每格减少 \(k\),用队列实现反复这个过程即可。由于每次队列操作至少使 \(\sum r\) 减少 \(1\),所以队列操作至少进行 \(\sum r\) 次,因此总复杂度 \(\mathcal O(nm + \sum r)\)。可以证明在模拟这个过程的时候,当前解不会超过任意一个可行解。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 500;
int n, m, ans, a[N + 10][N + 10], b[N + 10][N + 10];
queue<pii> q;
inline void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) {cin >> a[i][j]; if(a[i][j]) q.emplace(mp(i, j));}
while(!q.empty())
{
auto [i, j] = q.front(); q.pop();
if(b[i][j] >= a[i][j]) continue;
int num = (i > 1) + (i < n) + (j > 1) + (j < m);
int k = (a[i][j] - b[i][j] - 1) / (num + 1) + 1;
b[i][j] += k * (num + 1), ans += k;
if(i > 1) {b[i - 1][j] -= k; if(b[i - 1][j] < a[i - 1][j]) q.emplace(mp(i - 1, j));}
if(i < n) {b[i + 1][j] -= k; if(b[i + 1][j] < a[i + 1][j]) q.emplace(mp(i + 1, j));}
if(j > 1) {b[i][j - 1] -= k; if(b[i][j - 1] < a[i][j - 1]) q.emplace(mp(i, j - 1));}
if(j < m) {b[i][j + 1] -= k; if(b[i][j + 1] < a[i][j + 1]) q.emplace(mp(i, j + 1));}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 SHCPC inv E. 完美区间
太狠了。首先根号分治+分块的 \(\mathcal O(n \sqrt{n})\) 感觉烂完了,记一个神仙做法。考虑对每个数取倒数,联想一下任意不同的数两两互质,注意到实际上一个区间合法的充要条件是 \(\sum \frac{1}{x_i}\) 是整数。可以随便来几个模数取逆元,当区间和全部相等且比较小的情况下认为是整数,实际上只取两个模数正确性就已经非常高了。复杂度 \(\mathcal O(q \log n)\),代码非常简单不写了。
2026 SHCPC inv F. 神话子序列
小王写了一个半小时都没写完的东西,强烈谴责。一个数能被 \(9\) 整除,当且仅当各位数之和能被 \(9\) 整除,我们做模 \(9\) 意义下的数位前缀和,一个子序列是合法的,当且仅当这个子序列的模 \(9\) 前缀和两两不同。注意到只有 \(9\) 种余数,而长度为 \(k\) 的串有 \(k + 1\) 个前缀和,所以合法子序列的长度不会超过 \(8\),考虑对已经出现过的余数集合做状压 DP。定义 \(f_{S, i}\) 表示当前前缀和余数为 \(i\),已经出现的前缀和集合为 \(S\) 时最后一个被选字符在原串中的最小位置,边界为 \(f_{1, 0} = 0\);预处理 \(nxt_{i, j}\) 表示第 \(i\) 个位置后模 \(9\) 前缀和第一个为 \(j\) 的位置。枚举下一次选择的余数转移即可,答案为所有可达状态中 popcount 最大的那一个,同时记录一下转移路径就可以构造出方案了。复杂度 \(\mathcal O(n + 2^9 \times 9^2)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 5e6;
static const int M = 1 << 9;
static const int INF = LONG_LONG_MAX;
int n, ans, nxt[N + 10][10], f[M][9];
pii pre[M][9], p;
string s;
inline void solve()
{
cin >> n >> s, memset(f, 0x3f, sizeof f), memset(pre, 0, sizeof pre), f[0][0] = 0, ans = 0;
for(int i = 0; i <= 9; ++i) nxt[n + 1][i] = n + 1;
for(int i = n; i; --i) {for(int k = 0; k <= 9; ++k) nxt[i][k] = nxt[i + 1][k]; nxt[i][s[i - 1] - '0'] = i;}
for(int S = 0; S < M; ++S) for(int i = 0; i <= 8; ++i) if(f[S][i] <= n) for(int j = 1; j <= 8; ++j)
{
int k = (i + j) % 9;
if((!k) || ((S >> k) & 1) || (nxt[f[S][i] + 1][j] > n)) continue;
if(f[S | (1 << k)][k] > nxt[f[S][i] + 1][j]) f[S | (1 << k)][k] = nxt[f[S][i] + 1][j], pre[S | (1 << k)][k] = mp(S, i);
}
for(int S = 0; S < M; ++S) for(int i = 0; i <= 8; ++i) if(f[S][i] <= n && ans < __builtin_popcount(S)) ans = __builtin_popcount(S), p = mp(S, i);
vector<int> Ans;
while((int)Ans.size() < ans) Ans.eb(f[p.fi][p.se]), p = pre[p.fi][p.se];
cout << ans << " ", reverse(Ans.begin(), Ans.end());
for(auto x:Ans) cout << x << " ";
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 杭电夏季 D3 H. FWT
国共第一次合作。
依旧先考虑静态怎么做。不难发现 \(x_1\) 和 \(x_n\) 比较特殊,二进制下 \(1\) 的位置 \(x_i\) 是 \(x_1\) 的子集,\(x_i\) 又是 \(x_1\) 的子集。注意到二进制下每一位的答案是可以独立计算的,不妨记 \(v_i\) 为 \(x_i\) 在某一位的选择,显然只有 \(v_1 = 1 \land v_n = 1\),\(v_1 = 0 \land v_n = 0\) 和 \(v_1 = 1 \land v_n = 0\) 三种情况,而对于第一种情况有 \(\forall i \in (1, n),v_i = 1\),同样的第二种情况有\(\forall i \in (1, n),v_i = 0\),这两种都只有一种方案;而对于第三种情况,可以暴力枚举出去 \(1\) 和 \(n\) 中间位置的 \(2^{n - 2}\) 种取值情况,暴力检查每种转移合法,统计出来就是 \(1 \to 0\) 的转移方案数。
得到了独立计算的转移数后,要把每一位合并起来计算。考虑一个数位 DP 的过程来应对 \(l \leq x \leq r\) 的限制:\(f_{i, 0/1/2/3}\) 表示二进制位枚举到第 \(i\) 位,
- \(0\) 表示 \(x_1 = r\),\(x_n = l\);
- \(1\) 表示 \(x_1 = r\),\(x_n > l\);
- \(2\) 表示 \(x_1 < r\),\(x_n = l\);
- \(3\) 表示 \(x_1 < r\),\(x_n > l\);
我们可以得到一些转移方程,然后能够发现转移方程可以写成矩阵的形式,于是可以考虑用线段树维护矩阵乘法,修改就是去对应的更改线段树上的值即可,时间复杂度 \(\mathcal O(m 2^n + q T^2 log |S|)\),这里矩阵大小取 \(T = 4\).
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 1e5;
static const int M = 4;
static const int mod = 998244353;
int n, m, t, L, delta, ans;
int x, y, s, ori[N + 10];
string s1, s2;
pii b[N];
struct Matrix
{
int a[M][M]{0};
inline Matrix operator = (const Matrix &b)
{
for(int i = 0; i < M; ++i) for(int j = 0; j < M; ++j) this->a[i][j] = b.a[i][j];
return *this;
}
inline friend Matrix operator + (Matrix a, Matrix b)
{
for(int i = 0; i < M; ++i) for(int j = 0; j < M; ++j) (a.a[i][j] += b.a[i][j]) %= mod;
return a;
}
inline friend Matrix operator * (Matrix a, Matrix b)
{
Matrix res;
for(int i = 0; i < M; ++i) for(int j = 0; j < M; ++j) for(int k = 0; k < M; ++k) (res.a[i][j] += (a.a[i][k] * b.a[k][j] % mod)) %= mod;
return res;
}
};
Matrix all[4], info[N << 2], op;
void build(int i, int l, int r)
{
if(l == r) return info[i] = all[ori[l]], void();
int mid = (l + r) >> 1;
build(i << 1, l, mid), build(i << 1 | 1, mid + 1, r);
info[i] = info[i << 1] * info[i << 1 | 1];
}
void modify(int i, int l, int r, int x, int k)
{
if(l == r) return info[i] = all[k], void();
int mid = (l + r) >> 1;
if(x <= mid) modify(i << 1, l, mid, x, k); else modify(i << 1 | 1, mid + 1, r, x, k);
info[i] = info[i << 1] * info[i << 1 | 1];
}
inline void solve()
{
cin >> n >> m >> t, s = (1ll << (n - 2));
for(int i = 1; i <= m; ++i) cin >> b[i].fi >> b[i].se;
for(int i = 0; i < (1ll << (n - 2)); ++i)
{
bool flag = 0;
for(int j = 1; j <= m; ++j)
{
if(b[j].se == 1 && ((i >> (b[j].fi - 2)) & 1) == 0) flag = 1;
if(b[j].fi == n && ((i >> (b[j].se - 2)) & 1) == 1) flag = 1;
if(b[j].fi == 1 || b[j].se == 1 || b[j].fi == n || b[j].se == n) continue;
flag |= (((i >> (b[j].fi - 2)) & 1) == 0 && ((i >> (b[j].se - 2)) & 1) == 1);
}
s -= flag;
}
all[0].a[0][0] = 1, all[0].a[1][1] = 1, all[0].a[2][2] = s + 1, all[0].a[2][3] = 1, all[0].a[3][3] = s + 2;
all[1].a[0][0] = s, all[1].a[0][1] = 1, all[1].a[0][2] = 1, all[1].a[1][1] = s + 1, all[1].a[1][3] = 1, all[1].a[2][2] = s + 1, all[1].a[2][3] = 1, all[1].a[3][3] = s + 2;
all[2].a[1][1] = 1, all[2].a[2][2] = 1, all[2].a[3][3] = s + 2;
all[3].a[0][0] = 1, all[3].a[1][1] = s + 1, all[3].a[1][3] = 1, all[3].a[2][2] = 1, all[3].a[3][3] = s + 2;
cin >> s1 >> s2;
delta = (int)s2.size() - (int)s1.size();
s2 = '0' + s2, s1 = string(delta + 1, '0') + s1;
L = (int)s1.size() - 1;
auto get = [&](int x) -> int
{
if(s1[x] == '0' && s2[x] == '0') return 0;
if(s1[x] == '0' && s2[x] == '1') return 1;
if(s1[x] == '1' && s2[x] == '0') return 2;
return 3;
};
for(int i = 1; i <= L; ++i) ori[i] = get(i);
auto calc = [&]() -> int
{
memset(op.a, 0, sizeof op.a), op.a[0][0] = 1, op = op * info[1], ans = 0;
for(int i = 0; i < M; ++i) for(int j = 0; j < M; ++j) (ans += op.a[i][j]) %= mod;
return ans;
};
build(1, 1, L), cout << calc() << '\n';
for(int i = 1; i <= t; ++i)
{
cin >> x >> y;
if(x == 0) {y += delta; if(s1[y] == '0') s1[y] = '1'; else s1[y] = '0';}
if(x == 1) {if(s2[y] == '0') s2[y] = '1'; else s2[y] = '0';}
modify(1, 1, L, y, get(y)), cout << calc() << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 牛客夏季 D4 E. DPRS
依旧狠。猜测我们要让分母尽可能小,得出结论是一定选取的是相邻的两个节点。注意到最短路具有局部最优性,每次更改的边权又一定是改小,所以对于一条 \(s \to t\) 的最短路,如果修改了一条 \(x \to y\) 的边,最短路长度要么不变,要么变成 \(s \to x\) 的最短路加上 \(y \to t\) 的最短路加上改的这一条边。每次修改操作后我们暴力枚举每一条边,选取这条边作为分母,反向的最短路作为分子,看一下选哪条边答案最大即可。瓶颈在于第一遍预处理出全源最短路,复杂度 \(\mathcal O(nm \log n + qm)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 2e3;
int n, m, Q, x, y, z, id, k;
int dis[N + 10][N + 10], vis[N + 10];
double ans;
vector<pii> G[N + 10];
priority_queue<pii> q;
struct edge{int x, y, z;}E[N + 10];
inline void solve()
{
cin >> n >> m >> Q;
for(int i = 1; i <= n; ++i) G[i].clear();
for(int i = 1; i <= m; ++i) cin >> x >> y >> z, G[x].eb(mp(y, z)), E[i] = (edge){x, y, z};
auto dijkstra = [&](int st) -> void
{
for(int i = 1; i <= n; ++i) dis[st][i] = 1e18, vis[i] = 0;
q.emplace(mp(0, st)), dis[st][st] = 0;
while(!q.empty())
{
int now = q.top().se; q.pop();
if(vis[now]) continue;
vis[now] = 1;
for(auto [to, val]:G[now]) if(dis[st][to] > dis[st][now] + val) dis[st][to] = dis[st][now] + val, q.emplace(mp(-dis[st][to], to));
}
};
for(int i = 1; i <= n; ++i) dijkstra(i);
while(Q--)
{
cin >> id >> k, ans = 0;
for(int i = 1; i <= m; ++i)
{
if(i == id) ans = max(ans, ((double)(dis[E[i].y][E[i].x])) / ((double)k));
if(i != id) ans = max(ans, ((double)(min(dis[E[i].y][E[id].x] + dis[E[id].y][E[i].x] + k, dis[E[i].y][E[i].x]))) / ((double)E[i].z));
}
cout << fixed << setprecision(9) << ans << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 牛客夏季 D4 K. Decomposition Trees
好像和题解不一样。我们考虑做一个合并子树的 DP。定义 \(f_{u, i}\) 表示考虑到节点 \(u\),它是子树中第 \(i\) 个被删除的,并记 \(s\) 为 \(f\) 的后缀和,\(g\) 为转移 \(f\) 用的一个临时数组,可以得到转移方程 \(g_{i + j} = f_{u, i} \times s_{v, j} \times \binom{i + j}{i}\)。因为每个节点的合并是子树大小相关,所以时间复杂度和树上背包一样,因此是 \(\mathcal O(n^2)\) 的,但是这个式子形式非常卷积啊,上 FFT 是不是能 \(\mathcal O(n \log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 3e3;
static const int mod = 1e9 + 7;
int n, x, y, ans, C[N + 10][N + 10];
int f[N + 10][N + 10], suf[N + 10][N + 10], g[N + 10], siz[N + 10];
vector<int> G[N + 10];
void dfs(int now, int father)
{
f[now][0] = 1, siz[now] = 1;
for(auto to:G[now]) if(to != father)
{
dfs(to, now);
for(int i = 0; i < siz[now] + siz[to]; ++i) g[i] = 0;
for(int i = 0; i < siz[now]; ++i) for(int j = 0; j <= siz[to]; ++j) (g[i + j] += ((f[now][i] * suf[to][j] % mod) * C[i + j][j] % mod)) %= mod;
siz[now] += siz[to];
for(int i = 0; i < siz[now]; ++i) f[now][i] = g[i];
}
int sm = 0;
for(int i = siz[now] - 1; ~i; --i) (sm += f[now][i]) %= mod, suf[now][i + 1] = sm;
suf[now][0] = sm;
}
inline void solve()
{
cin >> n, C[0][0] = 1;
for(int i = 1; i <= n; ++i) {for(int j = 1; j <= n; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod; C[i][0] = 1;}
for(int i = 1; i < n; ++i) cin >> x >> y, G[x].eb(y), G[y].eb(x);
dfs(1, 0), cout << suf[1][0] << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
马蹄要金了么,真的假的?求你了,我有机会去百度之星吗。
2026 杭电夏季 D5 I. 自乘
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 1e3;
int n, x, y, flag, flg, cnt, a[N + 10], b[N + 10], tmp[2][N + 10], deg[N + 10];
int m, M, ans, _A[N + 10], _B[N + 10];
int st[N + 10], fst[N + 10][N + 10], Len[N + 10];
int Lop[N + 10], d[N + 10], r[N + 10], p[N + 10], col[N + 10], Ccnt[N + 10];
vector<int> G[N + 10], rG[N + 10];
queue<int> q;
void init()
{
for(int L = 1; L <= 1000; ++L)
{
int cnt = 0, x = 1 % L;
for(int i = 0; i <= 1000; ++i) fst[i][L] = -1;
fst[x][L] = cnt;
while(1)
{
(x *= 2) %= L, ++cnt;
if(!~fst[x][L]) fst[x][L] = cnt; else {st[L] = fst[x][L], Len[L] = cnt - fst[x][L]; break;}
}
}
}
void dfs(int now, int from)
{
col[now] = cnt;
if(!Lop[now]) ++Ccnt[cnt], d[now] = 0, r[now] = now, p[now] = Ccnt[cnt];
else d[now] = d[from] + 1, r[now] = r[from];
for(auto to:rG[now]) if(!col[to]) dfs(to, now);
}
inline bool check(int k)
{
for(int i = 1; i <= n; ++i) tmp[0][i] = a[i];
while(k--)
{
for(int i = 1; i <= n; ++i) tmp[1][i] = tmp[0][tmp[0][i]];
for(int i = 1; i <= n; ++i) tmp[0][i] = tmp[1][i];
}
for(int i = 1; i <= n; ++i) if(tmp[0][i] != b[i]) return 0;
return 1;
}
inline int lcm(int a, int b) {return a / __gcd(a, b) * b;}
inline int mul(int a, int b, int mod) {int ans = 0; while(b) {if(b & 1) (ans += a) %= mod; (a += a) %= mod, b >>= 1;} return ans;}
int exgcd(int a, int b, int &x, int &y)
{
if(!b) return x = 1, y = 0, a;
int g = exgcd(b, a % b, x, y);
int z = x; x = y, y = z - y * (a / b);
return g;
}
inline bool exCRT()
{
M = _A[1], ans = _B[1], x = y = 0;
for(int i = 2; i <= m; ++i)
{
int A = M, B = _A[i], a = _B[i], C = (a - ans % B + B) % B;
int g = exgcd(A, B, x, y);
flg |= (C % g != 0), x = mul(x, C / g, B), ans += x * M, M = lcm(M, B), ans = (ans % M + M) % M;
}
return !flg;
}
inline void solve()
{
cin >> n, flg = m = cnt = 0, flag = 1;
for(int i = 1; i <= n; ++i) Lop[i] = deg[i] = r[i] = d[i] = p[i] = col[i] = Ccnt[i] = 0, G[i].clear(), rG[i].clear();
for(int i = 1; i <= n; ++i) cin >> a[i], G[i].eb(a[i]), rG[a[i]].eb(i), ++deg[a[i]];
for(int i = 1; i <= n; ++i) cin >> b[i];
for(int i = 1; i <= n; ++i) if(a[i] != b[i]) flag = 0;
if(flag) return cout << 0 << '\n', void();
for(int i = 1; i <= n; ++i) if(!deg[i]) q.emplace(i);
while(!q.empty())
{
int now = q.front(); q.pop();
Lop[now] = 1;
for(auto to:G[now]) if(!--deg[to]) q.emplace(to);
}
for(int i = 1; i <= n; ++i) if(col[i] == 0 && Lop[i] == 0) ++cnt, dfs(i, 0);
for(int i = 1; i <= n; ++i) if(col[i] != col[b[i]]) return cout << -1 << '\n', void();
for(int i = 1; i <= n; ++i) if(Lop[b[i]] == 1)
{
int x = d[i] - d[b[i]], k = 0;
if((x <= 0) || (x & (x - 1))) return cout << -1 << '\n', void(); else return k = __lg(x), cout << (check(k) ? k : -1) << '\n', void();
}
int low = 0, mx = 0;
for (int i = 1; i <= n; i++) mx = max(mx, d[i]);
while ((1ll << low) < mx) low++;
for(int i = 1; i <= n; ++i)
{
int L = Ccnt[col[b[i]]];
int x = (p[r[i]] + d[i] - p[b[i]] + L) % L;
if(!~fst[x][L]) return cout << -1 << '\n', void();
if(fst[x][L] < st[L])
{
for(int k = 0; k < st[L]; ++k) if((1ll << k) % L == x % L) return cout << (check(k) ? k : -1) << '\n', void();
return cout << -1 << '\n', void();
}
else ++m, _A[m] = Len[L], _B[m] = fst[x][L], low = max(low, st[L]);
}
if (!exCRT()) return cout << -1 << '\n', void();
if (ans < low) ans += (low - ans + M - 1) / M * M;
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
init();
int T = 1; cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 牛客夏季 D6 J. Just Add and Divide
最后还是差一点改完,依旧空悲切。通过一步类似加权平均值,可以证明出答案中必定包含最大的 \(\max \frac{a_i}{b_i}\) 的 \(i\),找到最大的之后剩下的部分就是求 \(\max_{l \leq i \leq r} \frac{a_i - (-A)}{b_i - (-B)}\),看成把所有的 \((a_i, b_i)\) 放在一个二维平面上,求和 \((-A, -B)\) 构成的最大斜率是多少。这个经典问题使用分块维护凸包,斜率在凸包顶点上是单峰的所以查询在凸包上三分即可,复杂度 \(\mathcal O((n + q) \sqrt{n} \log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ld long double
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 1e5;
static const ld INF = 1e30L;
int n, m, op, x, y, a[N + 10], b[N + 10];
struct SegmentTree
{
int pos[N << 2];
void build(int i, int l, int r)
{
if(l == r) return pos[i] = l, void();
int mid = (l + r) >> 1;
build(i << 1, l, mid), build(i << 1 | 1, mid + 1, r);
pos[i] = (a[pos[i << 1]] * b[pos[i << 1 | 1]] > a[pos[i << 1 | 1]] * b[pos[i << 1]]) ? pos[i << 1] : pos[i << 1 | 1];
}
inline void build() {build(1, 1, n);}
void modify(int i, int l, int r, int x)
{
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) modify(i << 1, l, mid, x); else modify(i << 1 | 1, mid + 1, r, x);
pos[i] = (a[pos[i << 1]] * b[pos[i << 1 | 1]] > a[pos[i << 1 | 1]] * b[pos[i << 1]]) ? pos[i << 1] : pos[i << 1 | 1];
}
inline void modify(int x) {modify(1, 1, n, x);}
int query(int i, int l, int r, int L, int R)
{
if(l <= L && R <= r) return pos[i];
int mid = (L + R) >> 1, res1 = 0, res2 = 0;
if(l <= mid) res1 = query(i << 1, l, r, L, mid);
if(r > mid) res2 = query(i << 1 | 1, l, r, mid + 1, R);
if(!res1 || !res2) return res1 + res2;
return a[res1] * b[res2] > a[res2] * b[res1] ? res1 : res2;
}
inline int query(int l, int r) {return query(1, l, r, 1, n);}
}T;
struct Point
{
int u, v, id;
inline bool operator < (const Point &oth) const {return u != oth.u ? u < oth.u : v < oth.v;}
};
inline Point GetQ(int p) {return (Point){-b[p], -a[p], p};}
inline int cross(Point a, Point b, Point c) {return (b.u - a.u) * (c.v - a.v) - (b.v - a.v) * (c.u - a.u);}
struct Block
{
int bn, L[N + 10], R[N + 10], P[N + 10];
vector<Point> arr;
vector<vector<Point> > Blo, Hull;
ld slope(Point p, Point q) {return p.u != q.u ? ((ld)(p.v - q.v) / (p.u - q.u)) : (p.v > q.v ? INF : -INF);}
inline void rebuild(int b)
{
auto pts = Blo[b];
if(pts.empty()) return Hull[b].clear(), void();
sort(pts.begin(), pts.end());
vector<Point> tmp, up;
for(auto p:pts) if(!tmp.empty() && tmp.back().u == p.u) {if(tmp.back().v < p.v) tmp.back() = p;} else tmp.eb(p);
for(auto p:tmp)
{
while(up.size() >= 2 && cross(up[(int)up.size() - 2], up.back(), p) >= 0) up.pb();
up.eb(p);
}
Hull[b] = move(up);
}
inline pair<ld, int> queryHull(int b, Point q)
{
auto &h = Hull[b];
if(h.empty()) return mp(-INF, -1);
for(auto &p:h) if(p.u == q.u) {if(p.v > q.v) return mp(INF, p.id); break;}
int l = 0, r = (int)h.size() - 1;
while(r - l > 5)
{
int m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
ld s1 = slope(h[m1], q), s2 = slope(h[m2], q);
if(s1 < s2) l = m1; else r = m2;
}
pair<ld, int> ans = mp(-INF, -1);
for(int i = l; i <= r; ++i) if(slope(h[i], q) > ans.fi) ans = mp(slope(h[i], q), h[i].id);
return ans;
}
inline void build()
{
bn = sqrt(n);
for(int i = 1; i <= bn; ++i) L[i] = (i - 1) * bn + 1, R[i] = i * bn;
if(R[bn] < n) ++bn, L[bn] = R[bn - 1] + 1, R[bn] = n;
Blo.resize(bn + 1), Hull.resize(bn + 1), arr.resize(n + 1);
for(int i = 1; i <= bn; ++i) for(int j = L[i]; j <= R[i]; ++j) P[j] = i, arr[j] = (Point){b[j], a[j], j}, Blo[i].eb(arr[j]);
for(int i = 1; i <= bn; ++i) rebuild(i);
}
inline void modify(int pos, Point q) {arr[pos] = Blo[P[pos]][pos - L[P[pos]]] = q, rebuild(P[pos]);}
inline pair<ld, int> query(int l, int r, Point q)
{
int bl = P[l], br = P[r];
pair<ld, int> ans = mp(-INF, -1);
if(bl == br)
{
for(int i = l; i <= r; ++i) if(slope(arr[i], q) > ans.fi) ans = mp(slope(arr[i], q), i);
return ans;
}
for(int i = l; i <= R[bl]; ++i) if(slope(arr[i], q) > ans.fi) ans = mp(slope(arr[i], q), i);
for(int i = L[br]; i <= r; ++i) if(slope(arr[i], q) > ans.fi) ans = mp(slope(arr[i], q), i);
for(int i = bl + 1; i <= br - 1; ++i)
{
auto [curv, curid] = queryHull(i, q);
if(curv > ans.fi) ans = mp(curv, curid);
}
return ans;
}
}B;
inline void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= n; ++i) cin >> b[i];
T.build(), B.build();
for(int i = 1; i <= m; ++i)
{
cin >> op >> x >> y;
if(op == 1) a[x] = y, T.modify(x), B.modify(x, (Point){b[x], a[x], x});
if(op == 2) b[x] = y, T.modify(x), B.modify(x, (Point){b[x], a[x], x});
if(op == 3)
{
int P = T.query(x, y);
pair<ld, int> ans = mp(-INF, -1);
if(x <= P - 1)
{
auto cur = B.query(x, P - 1, GetQ(P));
if(cur.fi > ans.fi) ans = cur;
}
if(P + 1 <= y)
{
auto cur = B.query(P + 1, y, GetQ(P));
if(cur.fi > ans.fi) ans = cur;
}
int Q = ans.se;
int A = a[P] + a[Q];
int C = b[P] + b[Q];
int g = __gcd(A, C);
cout << A / g << ' ' << C / g << '\n';
}
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
2026 牛客夏季 D6 C. Pokémon GET daze!
我们把 \(X\) 在 \(Y\) 前面称作 \(X < Y\),考虑维护这些位次偏序关系。记 \(A_{i, j} = [r_i < b_j]\),\(B_{i, j} = [r_i < g_j]\),\(C_{i, j} = [b_i < g_i]\)。比如 \(b\) 在中间,那么有 \(r < b < g\) 或者 \(g < b < r\),我们会发现这两种情况都是 \(A = B = C\),对于 \(r\) 和 \(g\) 在中间的情况也是可以类比的,可以得到 \(r\) 在中间是 \(A \neq B = C\),\(g\) 在中间是 \(A = B \neq C\)。我们可以任意钦定一个初始值,然后等于号连 \(0\) 边,不等号连 \(1\) 边,dfs 的时候做异或操作即可。接下来通过获得的若干偏序关系,连边做拓扑排序就是一种合法的构造方法。复杂度 \(\mathcal O(n^2)\),难点在于一堆重编号。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
#define pb pop_back
#define mp make_pair
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
static const int N = 150;
static const int M = N * N * 3;
int X, Y, Z, all, cnt, ans[N * 3 + 10], deg[N * 3 + 10];
int val[M + 10], vis[M + 10], typ[M + 10];
char c;
queue<int> q;
vector<pii> g[M + 10];
vector<int> G[N * 3 + 10];
/*
A[i, j] r_i < b_j
B[i, j] r_i < g_j
C[i, j] b_i < g_j
*/
inline int id(int type, int i, int j) {return type == 1 ? (i - 1) * Z + j : (type == 2 ? X * Z + (i - 1) * Y + j : X * Y + X * Z + (i - 1) * Y + j);}
inline pii rid(int x) {return typ[x] == 1 ? mp((x - 1) / Z + 1, (x - 1) % Z + 1) : (typ[x] == 2 ? (x -= X * Z, mp((x - 1) / Y + 1, (x - 1) % Y + 1)) : (x -= X * Y + X * Z, mp((x - 1) / Y + 1, (x - 1) % Y + 1)));}
void dfs(int now)
{
vis[now] = 1;
for(auto [to, v]:g[now]) if(!vis[to]) val[to] = val[now] ^ v, dfs(to);
}
inline void solve()
{
cin >> X >> Y >> Z, all = X * Y + X * Z + Y * Z;
for(int i = 1; i <= X * Z; ++i) typ[i] = 1;
for(int i = 1; i <= X * Y; ++i) typ[X * Z + i] = 2;
for(int i = 1; i <= Y * Z; ++i) typ[X * Y + X * Z + i] = 3;
for(int i = 1; i <= X; ++i) for(int j = 1; j <= Y; ++j) for(int k = 1; k <= Z; ++k)
{
cin >> c;
int A = id(1, i, k), B = id(2, i, j), C = id(3, k, j);
if(c == 'B') g[A].eb(B, 0), g[B].eb(A, 0), g[A].eb(C, 0), g[C].eb(A, 0), g[B].eb(C, 0), g[C].eb(B, 0);
if(c == 'R') g[A].eb(B, 1), g[B].eb(A, 1), g[A].eb(C, 1), g[C].eb(A, 1), g[B].eb(C, 0), g[C].eb(B, 0);
if(c == 'G') g[A].eb(B, 0), g[B].eb(A, 0), g[A].eb(C, 1), g[C].eb(A, 1), g[B].eb(C, 1), g[C].eb(B, 1);
}
for(int i = 1; i <= all; ++i) if(!vis[i]) dfs(i);
for(int x = 1; x <= all; ++x)
{
auto [i, j] = rid(x);
if(typ[x] == 1) {if(val[x] == 1) G[i].eb(X + Y + j), ++deg[X + Y + j]; else G[X + Y + j].eb(i), ++deg[i];}
if(typ[x] == 2) {if(val[x] == 1) G[i].eb(X + j), ++deg[X + j]; else G[X + j].eb(i), ++deg[i];}
if(typ[x] == 3) {if(val[x] == 1) G[X + Y + i].eb(X + j), ++deg[X + j]; else G[X + j].eb(X + Y + i), ++deg[X + Y + i];}
}
for(int i = 1; i <= X + Y + Z; ++i) if(!deg[i]) q.emplace(i);
while(!q.empty())
{
auto now = q.front(); q.pop();
ans[now] = ++cnt;
for(auto to:G[now]) if(!--deg[to]) q.emplace(to);
}
for(int i = 1; i <= X; ++i) cout << ans[i] << " ";
cout << '\n';
for(int i = 1; i <= Y; ++i) cout << ans[i + X] << " ";
cout << '\n';
for(int i = 1; i <= Z; ++i) cout << ans[i + X + Y] << " ";
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; //cin >> T;
while(T--) solve();
return (0 - 0);
}
后来啊,你乐叔叔因为比赛使用了缺省源被判作弊了,没有金色的夏天,也没有百度之星的外卡。
每当我觉得生活要好起来了,就会再送我当头一棒,真的,我很痛苦。
2026 杭电夏季 D9 F. 好多石头
2026 牛客夏季 D10 D. Strict Majority
给定一棵 \(n\)个 点的树,部分点已标记小写字母,其余点需要补全。对任意至少包含3个点的简单路径,均不能存在某个字母出现次数严格超过路径点数的一半。构造任意一种合法标记方案,或者报告无解。
不难发现只要有长度为 \(3\) 的路径出现两个相同的字母就不合法了,那么对于一个节点,只需要判断它的 父亲-它-它的儿子 或者 它的儿子-它-它的儿子 这两种长度为三的路径即可。据此也可以得出一些性质:一个点的度数超过字符集大小一定无解,这也一定的启发了复杂度和字符集大小相关。考虑状压 DP,设 \(f_{i, u}\) 表示当前在节点 \(i\),它的父亲字母为 \(u\),他可以选择哪些字母。转移我们可以枚举它父亲的颜色以及它自身的颜色,这样已转移完的信息为 它选某个字母它的每一个儿子都可以选哪些字母,如果有一种匹配方式可以使得它和它的儿子们都有选择,那么这个字母的选择就是合法的。这个判断可以使用二分图最大匹配,儿子向可选的字母连边看是否都能匹配到一个字母,两侧的节点数量都不会超过字符集大小。构造方案也是同样的方法,二分图匹配时可以记录下分配到了哪个字母,作为解即可。综上复杂度 \(\mathcal O(|S|^{3} n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
#define eb emplace_back
#define mp make_pair
using namespace std;
static const int N = 5e3;
static const int S = 26;
int n, x, y, rtcol, ans[N + 10], col[N + 10], f[N + 10][S + 10];
int vis[S << 1], matL[S + 10], matR[S + 10];
string s;
vector<int> G[N + 10];
bool match(int now, const vector<int> &all)
{
int M = all[now];
for(int i = 0; i < S; ++i) if(!vis[i] && (M >> i & 1))
{
vis[i] = 1;
if(matR[i] == -1 || match(matR[i], all)) return matR[i] = now, matL[now] = i, 1;
}
return 0;
}
inline bool check(const vector<int> &all, vector<int> *asg = nullptr)
{
int res = all.size();
if(!res) return 1;
fill(matL, matL + res, -1), fill(matR, matR + S, -1);
for(int i = 0; i < res; ++i) {fill(vis, vis + S, 0); if(!match(i, all)) return 0;}
if(asg != nullptr)
{
asg->resize(res);
for(int i = 0; i < res; ++i) (*asg)[i] = matL[i];
}
return 1;
}
void dfs(int now, int father)
{
for(auto to:G[now]) if(to != father) dfs(to, now);
for(int colfa = 0; colfa <= S; ++colfa)
{
f[now][colfa] = 0;
for(int colnow = 0; colnow < S; ++colnow)
{
if((col[now] != -1 && colnow != col[now]) || (colfa != S && colnow == colfa)) continue;
bool flg = 0;
vector<int> chcol;
for(auto to:G[now]) if(to != father)
{
int M = f[to][colnow] & (~((colfa != S) << colfa)) & (~(1ll << colnow));
if(!M) {flg = 1; break;} else chcol.eb(M);
}
if(flg) continue;
if(check(chcol)) f[now][colfa] |= 1ll << colnow;
}
}
}
void rdfs(int now, int colnow, int father)
{
ans[now] = colnow;
vector<int> chcol;
for(auto to:G[now]) if(to != father) chcol.eb(f[to][colnow] & (~(1ll << colnow)) & (~((ans[father] != S) << ans[father])));
vector<int> asg;
if(chcol.size())
{
check(chcol, &asg);
int cnt = 0;
for(auto to:G[now]) if(to != father) rdfs(to, asg[cnt++], now);
}
}
void solve()
{
cin >> n >> s, rtcol = -1;
for(int i = 1; i <= n; ++i) col[i] = s[i - 1] == '?' ? -1 : (s[i - 1] - 'a'), G[i].clear(), ans[i] = 0;
for(int i = 1; i < n; ++i) cin >> x >> y, G[x].eb(y), G[y].eb(x);
for(int i = 1; i <= n; ++i) if(G[i].size() > 26) return cout << -1 << '\n', void();
dfs(1, 0), ans[0] = S;
if(!f[1][S]) return cout << -1 << '\n', void();
rtcol = __builtin_ctzll(f[1][S]), rdfs(1, rtcol, 0);
for(int i = 1; i <= n; ++i) cout << char('a' + ans[i]);
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T; cin >> T;
while (T--) solve();
return 0;
}
2026 杭电夏季 D10 G. 小白的烦恼
单点加一减一,全局第一个小于等于 \(k\) 的位置。注意到值的变化为 \(1\),所以每次操作完后可以快速得出新的最小值是多少。考虑在操作序列上做莫队,答案序列上做分块,对于每个块统计块内最小值,每次统计答案先找到第一个最小值小于等于 \(k\) 的块,再扫块的内部定位值的位置。有 \(n, m, q\) 同阶,所以复杂度 \(\mathcal O(n \sqrt{n})\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
#define eb emplace_back
#define mp make_pair
using namespace std;
static const int N = 2e5;
static const int M = 320;
int n, m, q, blo, bn, a[N + 10], t[N + 10], b[N + 10][M + 10], ori[N + 10];
int L[M + 10], R[M + 10], pos[N + 10], mx[M + 10];
struct query{int l, r, k, id, ans;}Q[N + 10];
inline void add(int x)
{
--b[ori[t[x]]][pos[t[x]]];
if(mx[pos[t[x]]] == ori[t[x]] && !b[ori[t[x]]][pos[t[x]]]) mx[pos[t[x]]] = ori[t[x]] + a[x];
ori[t[x]] += a[x], ++b[ori[t[x]]][pos[t[x]]];
if(ori[t[x]] < mx[pos[t[x]]]) mx[pos[t[x]]] = ori[t[x]];
}
inline void del(int x)
{
--b[ori[t[x]]][pos[t[x]]];
if(mx[pos[t[x]]] == ori[t[x]] && !b[ori[t[x]]][pos[t[x]]]) mx[pos[t[x]]] = ori[t[x]] - a[x];
ori[t[x]] -= a[x], ++b[ori[t[x]]][pos[t[x]]];
if(ori[t[x]] < mx[pos[t[x]]]) mx[pos[t[x]]] = ori[t[x]];
}
void solve()
{
cin >> n >> m >> q, blo = sqrt(m), bn = sqrt(n);
for(int i = 1; i <= n; ++i) cin >> t[i];
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= q; ++i) cin >> Q[i].l >> Q[i].r >> Q[i].k, Q[i].k += n, Q[i].id = i, Q[i].ans = -1;
sort(Q + 1, Q + q + 1, [&](query a, query b){return (a.l / bn) != (b.l / bn) ? (a.l / bn) < (b.l / bn) : ((a.l / bn) & 1) ? (a.r < b.r) : (a.r > b.r);});
for(int i = 1; i <= blo; ++i) L[i] = R[i - 1] + 1, R[i] = blo * i;
if(R[blo] < m) ++blo, L[blo] = R[blo - 1] + 1, R[blo] = m;
for(int i = 1; i <= blo; ++i)
{
for(int j = 0; j <= 2 * n; ++j) b[j][i] = 0;
b[n][i] = R[i] - L[i] + 1, mx[i] = n;
for(int j = L[i]; j <= R[i]; ++j) pos[j] = i, ori[j] = n;
}
for(int i = 1, l = 1, r = 0; i <= q; ++i)
{
while(l > Q[i].l) add(--l);
while(r < Q[i].r) add(++r);
while(l < Q[i].l) del(l++);
while(r > Q[i].r) del(r--);
for(int j = 1; j <= blo; ++j) if(mx[j] <= Q[i].k)
{
for(int o = L[j]; o <= R[j]; ++o) if(ori[o] <= Q[i].k) {Q[i].ans = o; break;}
break;
}
}
sort(Q + 1, Q + q + 1, [&](query a, query b){return a.id < b.id;});
for(int i = 1; i <= q; ++i) cout << Q[i].ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(nullptr);
int T; cin >> T;
while (T--) solve();
return 0;
}

浙公网安备 33010602011771号