Say 题选记(10.26 - 11.8)

P12029 [USACO25OPEN] Election Queries G

\(c_i\) 为投票给 \(i\) 的人数。那么两头奶牛 \(x\)\(y\) 能成为领队的条件是 \(c_x + c_y \ge c_{max}\),其中 \(c_{max} = \max{c_i}\)
考虑单次查询,用双指针可以轻松地做到 \(O(n)\),二分带个 \(\log\) 也可以接受的。
卡在了一个重要结论,由于 \(\sum{c_i} = n\),那么不同的 \(c_i\) 的取值最多有 \(O(\sqrt{n})\)\(1 + 2 + \cdots + n = \frac{n(n + 1)}{2}\))。
那么我们开 \(N\)\(set\) 记录 \(c_x = i\) 中最小/最大的 \(x\),然后每次对还存在的 \(c_i\) 做一遍尺取就好了。
复杂度 \(O(Q{\log{N} + \sqrt{N}})\)

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
set<int> s, mn[N];
set<int, greater<int> > mx[N];
int c[N], a[N], n, q;
void upd(int x, int k){
	if(c[x]){
		mx[c[x]].erase(x), mn[c[x]].erase(x);
		if(mx[c[x]].empty()) s.erase(c[x]);
	}
	c[x] += k;
	if(c[x]){
		mx[c[x]].insert(x), mn[c[x]].insert(x);
		if(mx[c[x]].size() == 1) s.insert(c[x]);
	}
}
int qry(){
	auto l = s.begin(), r = s.end();
	int mny = 1e9, cmax = *s.rbegin(), ans = 0;
//	cout << cmax << '\n';
	while(l != s.end()){
		while(r != s.begin() && *prev(r) + *l >= cmax) --r, mny = min(mny, *mn[*r].begin());
		ans = max(ans, *mx[*l].begin() - mny);
		++l;
	}
	return ans;
}
int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n >> q;
	for(int i = 1; i <= n; ++i) cin >> a[i], c[a[i]]++;
	for(int i = 1; i <= n; ++i){
		if(c[i]) s.insert(c[i]), mx[c[i]].insert(i), mn[c[i]].insert(i);
	}
	while(q--){
		int x, k;
		cin >> x >> k;
		upd(a[x], -1);
		a[x] = k;
		upd(a[x], 1);
		cout << qry() << '\n';
	}
	return 0;
}

P12030 [USACO25OPEN] OohMoo Milk G

怎么说呢,感觉这题很玄乎。
可以发现,Fj 每天的操作就是对最大的 \(A\) 个数 + 1,然后 Nj 的操作就是对最大的 \(B\) 个数 - 1。
我们发现 Fj 每天加的那 \(A\) 个数总是固定的,就是原序列中最大的 \(A\) 个数。那么现在难点在于 Nj 的操作。
先考虑对前 \(A\) 大的数全部加 \(D\)。对于一些特别大的数,Nj 每次操作都会选择减他们,相当于这些数占用了一些固定的位置。但对于一些更小的数来说,我们要最小化 \(\sum x^{2}\),根据凸性/均值,让他们更加平均是更优的。那么就类似云浅那场的 T2。我们考虑二分这个平均值 \(k\),对于 \(a_i > k + D\) 的,这就是那些每次都必须减的数,会耗费 \(D\) 个操作次数。而对于其他的 \(a_i > k\),我们会耗费 \(a_i - k\) 的操作次数把他们全部都调成这个平均值 \(k\)。由于题目限制总共操作次数不得超过 \(B \times D\)。我们找到最大的 \(k\) 使得操作次数刚好 \(\ge B \times D\)。假设 \(k\) 时耗费的次数是 \(check(k)\),根据上面调整的过程,不难发现我们二分出的这个 \(k\),它的 \(check(k) - B \times D < B\)。之所以会有这些超的,是因为整数均值就是一些是 \(k\),一些是 \(k + 1\)。那么我们把多的那部分再调成 \(k + 1\) 即可。

Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5, mod = 1e9 + 7;
int n, d, a, b, m[N];
int check(int k){
	int cnt = 0;
	for(int i = 1; i <= a; ++i){
		if(m[i] > k) cnt += min(d, m[i] - k);
	}
	return cnt;
}
signed main(){ 
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n >> d >> a >> b;
	for(int i = 1; i <= n; ++i) cin >> m[i];
	sort(m + 1, m + 1 + n, greater<int>());
	for(int i = 1; i <= a; ++i) m[i] += d;
	int l = 1, r = 2e9;
	while(l < r){
		int mid = (l + r + 1) >> 1;
		if(check(mid) < b * d) r = mid - 1;
		else l = mid;
	}
	int k = check(l) - b * d, ans = 0;
	for(int i = 1; i <= n; ++i){
		if(m[i] > l) m[i] -= min(d, m[i] - l);
		(ans += m[i] * m[i]) %= mod; 
	}
	(ans += k * (2 * l + 1)) %= mod;
	cout << ans;
	return 0;
}

P11842 [USACO25FEB] Bessie's Function G

认为 \(x \to a_x\) 连边。发现最后一定是所有点要么 \(x\) 是自环,要么 \(x\) 连到一个是自环的点。然后就可以基环树 dp。

Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int f[N][2], a[N], c[N], n, st[N], tp, g[2][N][2];
bitset<N> on, in, vis;
vector<int> e[N], ring;
void find(int u){
	vis[u] = 1, in[u] = 1, st[++tp] = u;
	int v = a[u];
	if(in[v]){
		for(int i = tp; i >= 1; --i){
			on[st[i]] = 1, ring.emplace_back(st[i]);
			if(st[i] == v) break;
		}
	}
	else find(v);
	in[u] = 0, --tp;
}
void dfs(int u){
	vis[u] = 1, f[u][1] = (a[u] == u ? 0 : c[u]);
	for(int v : e[u]){
		if(!on[v]){
			dfs(v);
			f[u][0] += f[v][1];
			f[u][1] += min(f[v][0], f[v][1]);
		}
	}
}
signed main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
		e[a[i]].emplace_back(i);
	}
	for(int i = 1; i <= n; ++i) cin >> c[i];
	int ans = 0;
	for(int i = 1; i <= n; ++i){
		if(vis[i]) continue;
		ring.clear();
		find(i);
		for(int u : ring) dfs(u);
		// cout << '\n';
		// for(int i = 1; i <= n; ++i) cout << f[i][0] << ' ' << f[i][1] << '\n';
 		int L = ring.size();
		if(L == 1){
			int x = ring[0];
			ans += min(f[x][0], f[x][1]);
			continue;
		}
		for(int j : {0, 1}){
			g[j][0][j ^ 1] = 1e18;
			g[j][0][j] = f[ring[0]][j];
			for(int i = 1; i < L; ++i){
				int u = ring[i];
				g[j][i][0] = g[j][i - 1][1] + f[u][0];
				g[j][i][1] = min(g[j][i - 1][0], g[j][i - 1][1]) + f[u][1];
			}
		}
		ans += min({g[0][L - 1][1], g[1][L - 1][0], g[1][L - 1][1]});
	}
	cout << ans << '\n';
	return 0;
}

P10083 [GDKOI2024 提高组] 不休陀螺

\(c_i = b_i - a_i\),一个陀螺无限首先要满足 \(\sum c_i \ge 0\)。然后贪心地把负的全部都放前面,假设所有 \(c_i < 0\) 的和是 \(S\)。那么要保证 \(E + S \ge a_i\),注意这只是对 \(a_i\) 自己的 \(c_i\) 不是负的情况,如果是负的话就是 \(E + S \ge a_i + c_i\)。那么就令 \(d_i = \begin{cases} a_i & c_i \ge 0 \\ a_i + c_i & c_i < 0\end{cases}\)。那么这样一个陀螺无限就得满足 \(\begin{cases} \sum c_i \ge 0 \\ E + S \ge \max{d_i} \end{cases}\)

枚举右端点 \(r\),发现满足第二个条件的 \(l\) 是一段区间,并且越往左越严格,可以二分。二分出 \(l\) 之后,再数点求出 \(i \in [l, r]\) 这一段中,\(\sum_{o = i}^r c_o \ge 0\) 的个数,这样两个条件都满足了。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
#define int long long
typedef tuple<int, int, int> tpi; 
int n;
int a[N], b[N], c[N], d[N], sum[N], st[N][21], s[N], E, ans;
int get(int l, int r){
	int d = __lg(r - l + 1);
	return max(st[l][d], st[r - (1 << d) + 1][d]);
}
bool check(int mid, int r){
    // cout << get(mid, r) << ' ' << mid << ' ' << r << '\n';
	return E + s[r] - s[mid - 1] >= get(mid, r);
}
tpi p[2 * N];
struct BIT{
	int tr[N];
	#define lb(x) (x & (-x))
	void add(int x, int k){
		for(int i = x; i <= n; i += lb(i)) tr[i] += k;
	} 
	int qry(int x){
		int ret = 0;
		for(int i = x; i; i -= lb(i)) ret += tr[i];
		return ret; 
	}
}tr;
signed main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	// freopen("top.in", "r", stdin);
	// freopen("top.out", "w", stdout);
	cin >> n >> E;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	for(int i = 1; i <= n; ++i) cin >> b[i], c[i] = b[i] - a[i];
	for(int i = 1; i <= n; ++i){
        // cout << c[i] << ' ';
		sum[i] = c[i];
		sum[i] += sum[i - 1];
		if(c[i] < 0) s[i] = c[i], st[i][0] = a[i] + c[i];
		else st[i][0] = a[i];
		s[i] += s[i - 1];
	}
	for(int j = 1; (1 << j) <= n; ++j){
		for(int i = 1; i + (1 << j) - 1 <= n; ++i){
			st[i][j] = max(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
		}
	}
	int tot = 0;
	for(int r = 1; r <= n; ++r){
		int pl = 1, pr = r;
		while(pl < pr){
			int mid = (pl + pr) >> 1;
			if(check(mid, r)) pr = mid;
			else pl = mid + 1;
		}
		if(!check(pl, r)){
			// assert(pl == r);
			continue;
		}
		ans += (r - pl + 1); 
		// cout << pl << ' ' << r << '\n';
		p[++tot] = tpi{sum[r], pl, r};
	}
	for(int i = 1; i <= n; ++i) p[++tot] = tpi{sum[i - 1], -1e9, i};
	sort(p + 1, p + 1 + tot, greater<tpi>());
	for(int i = 1; i <= tot; ++i){
		int l = get<1>(p[i]), r = get<2>(p[i]);
//		cout << get<0>(p[i]) << ' ' << l << ' ' << r << '\n';
		if(l == -1e9) tr.add(r, 1);
		else{
			ans -= tr.qry(r) - tr.qry(l - 1);
//			cout << ans << '\n';
		} 
	} 
	cout << ans << '\n';
	return 0;
}

P10080 [GDKOI2024 提高组] 匹配

调整法。先跑一遍最大匹配,看看有没有完美匹配。没有直接 -1。然后我们考虑残量网络。如果此时一条正向边剩余流量是 0,那意味着这条边被选了。我们看一下所有的这些边的权值异或是否是 0,如果是直接输出。如果不是考虑调整。因为网络流的退流操作在这里正好就是从选/不选一条边->不选/选一条边,就看退的是正向边还是反向边。那么我们找一条权值是奇数的环(注意到这个显然只能在中间的那些点之间),然后对这条环上的边全部反选,找不到就 -1 了。
问题变成了找一个权值为奇数的环。这个 dfs 找,然后记录每个点进来的时候的权值即可,这样找环就是线性的了。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 5;
struct edge{
	int u, v, w, f, id, pre;
}e[N * N];
int s, t, tot = 1, head[N], n, m, cur[N], dep[N];
void adde(int u, int v, int w, int idx = 0){
	e[++tot] = {u, v, w, 1, idx, head[u]};
	head[u] = tot;
	e[++tot] = {v, u, w, 0, idx, head[v]};
	head[v] = tot;
}
bool bfs(){
	memset(dep, 0, sizeof(dep));
	memcpy(cur, head, sizeof(cur));
	dep[s] = 1;
	queue<int> q;
	q.push(s);
	while(!q.empty()){
		int u = q.front(); q.pop();
		for(int k = head[u]; k; k = e[k].pre){
			int v = e[k].v;
			if(e[k].f){
				if(!dep[v]){
					dep[v] = dep[u] + 1;
					q.push(v);
				}
			}
		}
	}
	return dep[t];
}
int dinic(int u, int ff){
	if(u == t) return ff;
	int su = 0;
	for(int k = cur[u]; k; k = e[k].pre){
		if(e[k].f){
			cur[u] = k;
			int v = e[k].v;
			if(dep[v] == dep[u] + 1){
				int sv = dinic(v, min(ff - su, e[k].f));
				e[k].f -= sv, e[k ^ 1].f += sv;
				su += sv; if(su == ff) return ff;
			}
		}
	}
	if(!su) dep[u] = 0;
	return su;
}
bool vis[N][2], in[N][2];
int st[N], ste[N], tp; 
bool fl;
void dfs(int u, int w){
	if(vis[u][w]) return;
	vis[u][w] = 1; in[u][w] = 1;
	for(int k = head[u]; k; k = e[k].pre){
		int v = e[k].v, d = e[k].w, f = e[k].f;
		if(!f) continue;
		int nxt = w ^ d;
		if(in[v][nxt ^ 1]){
			e[k].f--, e[k ^ 1].f++;
			for(int i = tp; i >= 1; --i){
				if(st[i] == v) break;
				int o = ste[i];
				e[o].f--, e[o ^ 1].f++;
			}
			fl = 1;
			in[u][w] = 0;
			return;
		}
		st[++tp] = v, ste[tp] = k;
		dfs(v, nxt);
		--tp;
		if(fl){
			in[u][w] = 0;
			return;
		}
	}
	in[u][w] = 0;
}
void solve(){
	cin >> n >> m;
	tot = 1; memset(head, 0, sizeof(head));
	s = 2 * n + 1, t = 2 * n + 2;
	for(int i = 1; i <= m; ++i){
		int u, v, w; cin >> u >> v >> w;
		adde(u, v, w, i);
	}
	for(int i = 1; i <= n; ++i) adde(s, i, 0);
	for(int i = n + 1; i <= 2 * n; ++i) adde(i, t, 0);
	int f = 0;
	while(bfs()) f += dinic(s, n);
	if(f < n) return cout << "-1\n", void();
	int res = 0;
	for(int i = 2; i <= tot; i += 2){
		if(!e[i].f) res ^= e[i].w; 
	}
	if(res == 1){
		fl = 0;
		memset(vis, 0, sizeof(vis));
		for(int i = 1; i <= 2 * n; ++i){
			st[++tp] = i;
			dfs(i, 0);
			--tp;
			if(fl) break;
		}
		if(!fl) return cout << "-1\n", void();
	}
	for(int i = 2; i <= tot; i += 2){
		if(!e[i].f && e[i].id) cout << e[i].id << ' ';
	}
	cout << '\n';
}
int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

P10085 [GDKOI2024 提高组] 染色

人类智慧题。一次操作反转自己和周围的四个格子。如果我们再对这四个格子做一次操作,发现变成了一个臂长是 3 的十字架形。然后再对这四个十字架的做臂长是 3 的操作,就变成了臂长是 5 的十字架。因此我们实际上可以做的就是臂长为 \(2^k + 1\) 的十字架。然后由于棋盘是旋转相邻的,到 \(2^n + 1\) 时,正好上下相消,左右相消。所以最终棋盘上一个点是 1,就是要求我们对这个点构造一个 \(2^n + 1\) 的十字架。这又等价于在 \(2^{n - 1} + 1\) 的四个点处(和它本身)构造一个 \(2^{n - 1} + 1\) 的十字架。然后从大到小传就行,边界就是 \(n = 1\) 时,要对所有棋盘上为 1 的点做操作。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2048;
int a[N][N], n, b[N][N];
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    int d = (1 << n);
    for(int i = 0; i < d; ++i){
        for(int j = 0; j < d; ++j){
            cin >> a[i][j];
        }
    }
    for(int w = n; w >= 1; --w){
        memset(b, 0, sizeof(b));
        int k = 1 << (w - 1);
        for(int i = 0; i < d; ++i){
            for(int j = 0; j < d; ++j){
                if(a[i][j]){
                    b[i][j] ^= 1;
                    b[(i + k) % d][j] ^= 1;
                    b[i][(j + k) % d] ^= 1;
                    b[(d + i - k) % d][j] ^= 1;
                    b[i][(d + j - k) % d] ^= 1;
                }
            }
        }
        memcpy(a, b, sizeof(b));
    }
    int cnt = 0;
    for(int i = 0; i < d; ++i){
        for(int j = 0; j < d; ++j){
            cnt += a[i][j];
        }
    }
    cout << cnt << '\n';
    for(int i = 0; i < d; ++i){
        for(int j = 0; j < d; ++j){
            if(a[i][j]) cout << i << ' ' << j << '\n';
        }
    }
    return 0;
}

P10084 [GDKOI2024 提高组] 计算

先看这个 \(F(m, a, b)\) 怎么算。就这个 \(F(m, a, b) = \gcd(m^a - 1, m^b - 1) + 1\),不妨设 \(a \ge b\),那么 \(F(m, a, b) = \gcd(m^b - 1, m^b(m^{a - b} - 1)) + 1\),然后 \(m^b - 1\)\(m^b\) 互质,所以也就是 \(F(m, a, b) = \gcd(m^b - 1, m^{a - b} - 1) + 1\)。考虑一些边界,得出 \(F(m, a, b) = m^{\gcd(a, b)}\)
那么 \(L\)\(R\) 中每种同余类都有 \(n = \frac{R - L + 1}{m}\) 个数。我们最终要求的就是:

\[\begin{aligned} G(x) = \prod_{i = 0}^{m - 1} (x^i + 1)^n \\ Ans = \sum_{k = 0}^{\infty}[m | k][x^k]G(x) \end{aligned} \]

普通的单位根反演是 \([m | k] = \frac{1}{m} \sum_{i = 0}^{m - 1} \omega_m^{ik}\)。一个更加常用的推论是 $$\sum_{k = 0}^n [m | k][x^k]f(x) = \sum_{k = 0}^n \frac{1}{m} \sum_{i = 0}^{m - 1} \omega_m^{ik} [x^k]f(x) = \frac{1}{m} \sum_{i = 0}^{m - 1}f(\omega_m^i)$$
套用这里的结论,我们的 \(Ans = \frac{1}{m}\sum_{i = 0}^{m - 1}\prod_{j = 0}^{m - 1}(\omega_m^{ij} + 1)^n\)
根据xft的数论课。这里的单位跟的指数是在 \(m\) 的环上以 \(j\) 为步长走 \(m\) 步。考虑枚举间隔 \(g = \gcd(m, j)\),这样一个环会走 \(g\) 次。就是

\[Ans = \frac{1}{m} \sum_g [g | m] \varphi(\frac{m}{g}) \prod_{j = 0}^{\frac{m}{g} - 1} (\omega_{\frac{m}{g}}^j + 1)^{ng} \]

注意 \(\prod_{j = 0}^{t - 1}(\omega_t^j + 1) = [2 \nmid t] 2\),在 \(x^t - 1 = \prod_{j = 0}^{t - 1}(x - \omega_t^j)\) 带入 -1 可得。
所以最后就是 \(Ans = \frac{1}{m} \sum_g[g | m] \varphi(\frac{m}{g}) [2 \nmid \frac{m}{g}] 2 ^ {ng}\)。暴力算即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e7 + 5, mod = 998244353;
bitset<N> vis;
int pri[N], cnt, phi[N], g[N]; 
ll F(ll m, int a, int b){
    if(a == 0 || b == 0) return 0;
    int d = __gcd(a, b); ll ret = 1;
    for(int i = 1; i <= d; ++i) ret = ret * m;
    return ret; 
}
ll qpow(ll a, ll b){
    ll ret = 1;
    for(;b ; b >>= 1, a = (a * a) % mod){
        if(b & 1) ret = (ret * a) % mod;
    }
    return ret;
}
void solve(){
    int m, a, b, c, d;
    cin >> m >> a >> b >> c >> d;
    ll L = F(m, a, b), R = F(m, c, d), n = (R - L) / m, ans = 0;
    auto cal = [&](ll g){
        return ((m / g) % 2 ? (phi[m / g] * qpow(2, (n * g) % (mod - 1)) % mod) : 0);
    };
    for(int i = 1; i <= m / i; ++i){
        if(m % i == 0){
            ans = (ans + cal(i)) % mod;
            if(i != m / i) ans = (ans + cal(m / i)) % mod;
        }
    }
    cout << ans * qpow(m, mod - 2) % mod << '\n';
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    phi[1] = 1;
    for(int i = 2; i <= 1e7; ++i){
        if(!vis[i]){
            pri[++cnt] = i;
            g[i] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt; ++j){
            int x = i * pri[j];
            if(x > 1e7) break;
            vis[x] = 1;
            if(i % pri[j] == 0){
                g[x] = g[i] * pri[j];
                if(g[x] == x) phi[x] = i * phi[pri[j]];
                else phi[x] = phi[g[x]] * phi[x / g[x]];
                break;
            }
            g[x] = pri[j];
            phi[x] = phi[i] * phi[pri[j]];
        }
    }
    int T; cin >> T;
    while(T--) solve();
    return 0;
}
posted @ 2025-11-05 11:38  Hengsber  阅读(17)  评论(0)    收藏  举报