妄想感傷代償連盟 | 2023.7.14做题记录
妄想感傷代償連盟 | 2023.7.14做题记录
前几周都在组题和打一些神秘模拟赛,现在终于有空写做题记录了
决定多vp点模拟赛,希望可以稍微增长一点比赛水平。
SDOI2016 Day1
A. table
先打了个暴力,然后想想 \(k=0\) 怎么做
逐位考虑贡献,分别计算 \(n,m\) 里面有多少个数 \(pos_i\) 为 \(0/1\),然后加上这部分对异或的贡献即可
k=0的暴力
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
typedef long long ll;
const int N = 55;
//每位有多少个数是0/1
ll num[2][N];
inline ll read() {
ll s = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return s;
}
ll n, m, k, p;
ll reg(ll x) {
return (x % p + p) % p;
}
ll calc() {
if (k == 1) {
return reg(reg(n) * reg(m) % p - std::min(n, m) % p);
}
return 0;
}
int main() {
int T = read();
while (T--) {
n = read(), m = read(), k = read(), p = read();
for (int x = 0; x < N; x++) {
num[0][x] = m / (1ll << (x + 1));
num[1][x] = m / (1ll << (x + 1));
num[0][x] *= 1ll << x;
num[1][x] *= 1ll << x;
ll res = m % (1ll << (x + 1));
if (res > (1ll << x))
num[0][x] += 1ll << x, num[1][x] += (res - (1ll << x));
else
num[0][x] += res;
}
ll ans = 0;
for (int x = 0; x < N; x++) {
ll num0 = 0, num1 = 0;
num0 = n / (1ll << (x + 1));
num1 = n / (1ll << (x + 1));
num0 *= 1ll << x;
num1 *= 1ll << x;
ll res = n % (1ll << (x + 1));
if (res > (1ll << x))
num0 += 1ll << x, num1 += (res - (1ll << x));
else
num0 += res;
ans = (ans + num0 % p * (num[1][x] % p) % p * ((1ll << x) % p) % p) % p;
ans = (ans + num1 % p * (num[0][x] % p) % p * ((1ll << x) % p) % p) % p;
}
ll fix = 0;
if (k >= 1) {
fix = calc();
}
printf("%lld\n", reg(ans - fix));
}
return 0;
}
然后考虑加上 \(k\) 的贡献怎么做!实际上就是求
这个显然很难做,所以我们考虑拆开两部分来做,一部分是 \(i\operatorname{xor} j\ge k\),我们对这部分计数,求和,最后减去 \(num\times k\),另一部分贡献是 \(0\),不管他。
注意到低位和高位没关系,那我们可以考虑一个二进制下的数位DP,接下来令 \(n\leftarrow n-1,m\leftarrow m-1\)
不妨假设 \(f_{u,0/1,0/1,0/1}\),表示当前为第 \(u\) 位, 前 \(u\) 位与 \(n\) 是否相同 / 前 \(u\) 位与 \(m\) 是否相同 / \(i\operatorname {xor} j\) 前 \(u\) 位与 \(k\) 是否相同时,所有可能 \(i\operatorname{xor} j\) 的情况的和,再设 \(g_{u,0/1,0/1,0/1}\) 表示合法情况的方案数
Code
int p1 = (n >> len - u) & 1, p2 = (m >> len - u) & 1, p3 = (k >> len - u) & 1;
int mxi = n1 ? p1 : 1;
int mxj = m1 ? p2 : 1;
for (int i = 0; i <= mxi; i++)
for (int j = 0; j <= mxj; j++) {
//如果压着 k 取,这里不能小于
if (k1 && (i ^ j) < p3) continue;
auto t = dfs(u + 1, n1 && (i == p1), m1 && (j == p2), k1 && ((i^j) == p3));
f[u][n1][m1][k1].first = (f[u][n1][m1][k1].first + t.first) % p;
f[u][n1][m1][k1].second = (f[u][n1][m1][k1].second + ((1ll << len - u) * (i ^ j) % p * t.first % p + t.second) % p) % p;
}
那实际上就不难有这种东西,其中 f[u][n1][m1][k1].first 表示的是 \(g_{u,n1,m1,k1}\),而 f[u][n1][m1][k1].second 则表示 \(f_{u,n1,m1,k1}\)
B. pair
一眼费用流
显然每个点有容量 \(b_i\),两个点连边的费用就是 \(c_ic_j\),一直跑最大费用最大流,直到收益为负即可。
问题就是如何把图建出来,一开始的想法是每个点复制左右各一份,但是这样限制就是 \(s\rightarrow u_l\) 的容量加上 \(u_r\rightarrow t\) 的容量不超过 \(b_u\)
然后有一个很天才的想法就是,对每个数 \(x\) 质因数分解,其结果记为 \(cnt_x\),所有左部点 \(cnt\) 为奇数,右部点 \(cnt\) 为偶数,如果两个数 \(x, y\) 满足 \(x|y\) 而且 \(cnt_y=cnt_x+1\) 就 \(x\) 向 \(y\) 连一条边
然后直接费用流就行
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
#include <queue>
typedef long long ll;
const int N = 210, M = N * N;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, m;
//{{{MCMF
int idx;
std::vector<int> G[N];
struct edge {
int u, v;
ll cap, f, c;
edge() {}
edge(int u, int v, ll cap, ll f, ll c) : u(u), v(v), cap(cap), f(f), c(c) { }
}E[N * N];
inline void add(int u, int v, ll f, ll c) {
E[idx] = edge(u, v, f, 0, c), G[u].push_back(idx++);
E[idx] = edge(v, u, 0, 0, -c), G[v].push_back(idx++);
}
int s, t;
ll Flow, Cost;
bool vis[N];
ll dep[N];
int now[N];
bool SPFA() {
memset(dep, 0x3f, (n + 3) * sizeof(ll));
memset(vis, 0, (n + 3) * sizeof(bool));
std::deque<int> q;
q.push_back(t);
dep[t] = 0, vis[t] = 1;
while (!q.empty()) {
int u = q.front();
q.pop_front();
vis[u] = 0;
if (!q.empty() && dep[q.front()] > dep[q.back()]) std::swap(q.front(), q.back());
for (int i : G[u]) {
auto &e1 = E[i], &e2 = E[i ^ 1];
if (e2.cap > e2.f && dep[e1.v] > dep[e1.u] - e1.c) {
dep[e1.v] = dep[e1.u] - e1.c;
if (!vis[e1.v]) {
if (!q.empty() && dep[e1.v] > dep[q.front()])
q.push_back(e1.v);
else
q.push_front(e1.v);
vis[e1.v] = 1;
}
}
}
}
return dep[s] < inf;
}
ll globalC = 0;
ll dfs(int u, ll ans) {
vis[u] = 1;
if (u == t || !ans) return ans;
ll flow = 0, k = 0;
for (int i = now[u]; i < G[u].size(); i++) {
now[u] = i;
auto &e1 = E[G[u][i]], &e2 = E[G[u][i] ^ 1];
if (vis[e1.v]) continue;
if (e1.cap > e1.f && dep[e1.v] == dep[e1.u] - e1.c) {
k = dfs(e1.v, std::min(ans, e1.cap - e1.f));
if (k == 0) continue;
e1.f += k, e2.f -= k;
flow += k, ans -= k;
globalC += e1.c;
if (!ans) break;
}
}
return flow;
}
//}}}
ll a[N], b[N], c[N];
int cnt[N];
int div(ll x) {
ll t = x;
int c = 0;
for (ll i = 2; i * i <= t; i++)
if (x % i == 0) {
while (x % i == 0) x /= i, c++;
}
if (x > 1) c++;
return c;
}
int main() {
std::cin >> n;
s = 0, t = n + 1;
for (int i = 1; i <= n; i++)
std::cin >> a[i];
for (int i = 1; i <= n; i++)
std::cin >> b[i];
for (int i = 1; i <= n; i++)
std::cin >> c[i];
for (int i = 1; i <= n; i++)
cnt[i] = div(a[i]);
for (int i = 1; i <= n; i++)
//奇数点连左边
if (cnt[i] & 1)
add(s, i, b[i], 0);
else
add(i, t, b[i], 0);
for (int i = 1; i <= n; i++) {
if (cnt[i] & 1) {
for (int j = 1; j <= n; j++)
if ((a[i] % a[j] == 0 || a[j] % a[i] == 0) && abs(cnt[i] - cnt[j]) == 1)
add(i, j, inf, -c[i] * c[j]);
}
}
while (SPFA()) {
vis[t] = 1;
while (vis[t] == 1) {
globalC = 0;
memset(vis, 0, (n + 3) * sizeof(bool));
memset(now, 0, (n + 3) * sizeof(int));
ll flow = dfs(s, inf);
//费用不能 > 0;
if (Cost + flow * globalC <= 0) {
Flow += flow;
Cost += flow * globalC;
} else {
ll t = Cost / globalC;
Flow += -t;
goto end;
}
}
}
end:
printf("%lld\n", Flow);
return 0;
}
C. game
树剖李超树,鸽了

浙公网安备 33010602011771号