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;
}
posted @ 2026-07-02 11:12  LgxTpre  阅读(99)  评论(0)    收藏  举报