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}\) 个数。我们最终要求的就是:
普通的单位根反演是 \([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\) 次。就是
注意 \(\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;
}

浙公网安备 33010602011771号