妄想感傷代償連盟 | 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\) 的贡献怎么做!实际上就是求

\[\sum_{i=0}^{n-1}\sum_{j=0}^{m-1}\max(i\operatorname{xor} j-k,0) \]

这个显然很难做,所以我们考虑拆开两部分来做,一部分是 \(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

树剖李超树,鸽了

posted @ 2023-07-15 12:57  Nelofus  阅读(44)  评论(0)    收藏  举报