11.18训练赛
A.P1103 书本整理
显然的dp,dp[i][j]代表前i本书留下j本的最优答案,答案为dp[n][n - k]。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define dbg(x) cout << #x << '=' << x << ' '
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
#define f first
#define s second
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;
const ll N = 1e7;
ll t, n, m, k, ans = N;
ll x[N];
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> k;
pair<ll, ll> p[n + 1];
fu(i, 1, n) cin >> p[i].f >> p[i].s;
sort(p + 1, p + n + 1);
ll dp[n + 1][n - k + 1];
memset(dp, 60, sizeof dp), dp[0][0] = 0;
fu(i, 1, n) fu(j, 1, n - k) {
fd(i1, i - 1, max(i - k - 1, 0ll)) {
t = i1 ? abs(p[i].s - p[i1].s) : 0;
mn(dp[i][j], dp[i1][j - 1] + t);
}
mn(ans, dp[i][n - k]);
}
cout << ans;
}
B.P4389 付公主的背包
这题的前置知识是生成函数与多项式,这里简单写一下,不详细推。
对体积为V的物品构建生成函数\(f(x) = 1 + x^v + x^{2v} + ... = \frac{1}{1 - x^v}\),
将所有的多项式相乘,次数对应的系数就是体积等于次数时的方案数。
直接做卷积显然会T,考虑取对数有\(\ln(\frac{1}{1 - x^v}) = \sum_{i >= 1} \frac{x^{vi}}{i}\)
那么我们就可以在\(mlogm\)时间内得到答案的对数,再多项式exp即可,套一个多项式模板就可以过,
代码等我整理完多项式模板再放。
#include <bits/stdc++.h>
#define fu(a, b, c) for (int a = b; a <= c; a++)
#define fd(a, b, c) for (int a = b; a >= c; a--)
#define dbg(x) cout << #x << ':' << ' '
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
#define sz(x) (int)x.size()
using namespace std;
using ll = long long;
using poly = vector<int>;
const int N = (1 << 18) + 1, X = 3, M = 998244353;
int n, m, mx, k, p;
int f[N], g[N];
int iv[N], r[20][N], xi[N];
ostream &operator<<(ostream &cout, const vector<int> &v) {
for (int i : v)
cout << (i + M) % M << ' ';
cout << '\n';
return cout;
}
inline int up(int n) {
--n;
return n |= n >> 16, n |= n >> 8, n |= n >> 4, n |= n >> 2, n |= n >> 1, ++n;
}
inline int qpow(int a, int b) {
int r = 1;
for (; b; b >>= 1, a = (ll)a * a % M)
if (b & 1)
r = (ll)r * a % M;
return r;
}
inline void init_rev(const int &l, const int &n) {
fu(i, 0, n - 1) r[l][i] = r[l][i / 2] / 2 | (i & 1 ? n / 2 : 0);
}
inline void init(const int &n) {
iv[1] = xi[0] = 1;
fu(i, 2, n) iv[i] = (ll)-M / i * iv[M % i] % M;
xi[1] = qpow(X, (M - 1) / n);
fu(i, 2, n) xi[i] = (ll)xi[i - 1] * xi[1] % M;
for (ll i = 1, l = 2; l <= n; l <<= 1, ++i)
init_rev(i, l);
}
inline void ntt(poly &a, const int &n, const bool &f) {
a.resize(n);
ll l = __lg(n);
fu(i, 0, n - 1) if (i < r[l][i]) swap(a[i], a[r[l][i]]);
int p = mx >> 1;
for (int l = 1; l < n; l <<= 1, p >>= 1) {
for (int i = 0; i < n; i += l << 1) {
for (int j = i, x = 0; j < i + l; ++j, x += p) {
int b = (ll)xi[x] * a[j + l] % M;
a[j + l] = (a[j] - b) % M, a[j] = (a[j] + b) % M;
}
}
}
if (!f) {
reverse(&a[1], &a[n]);
fu(i, 0, n - 1) a[i] = (ll)a[i] * iv[n] % M;
}
}
inline poly der(const poly &a) {
poly b(sz(a));
fu(i, 0, sz(b) - 2) b[i] = ll(i + 1) * a[i + 1] % M;
return b;
}
inline poly inte(const poly &a) {
poly b(sz(a));
fd(i, sz(b) - 2, 0) b[i + 1] = (ll)a[i] * iv[i + 1] % M;
return b;
}
inline void mul(
poly &a, poly b, int n,
function<int(int, int)> f = [](const int &a, const int &b) { return (ll)a * b % M; }) {
ntt(a, n, 1), ntt(b, n, 1);
fu(i, 0, n - 1) a[i] = f(a[i], b[i]);
ntt(a, n, 0);
}
inline poly inv(const poly &a) {
poly b(1, qpow(a[0], M - 2));
for (int m = 2; m >> 1 < sz(a); m <<= 1) {
poly c(&a[0], &a[m]);
mul(b, c, m * 2, [](const int &a, const int &b) { return (2 - (ll)a * b) % M * a % M; });
b.resize(m);
}
return b;
}
inline poly ln(const poly &a) {
int n = up(sz(a) * 2 - 1);
poly b = der(a), c = inv(a);
mul(b, c, n);
return inte(b);
}
inline poly exp(const poly &a) {
poly b(1, 1);
for (int m = 2; m >> 1 < sz(a); m <<= 1) {
poly c = ln(b);
c[0] = 1, c.resize(m);
fu(i, 1, m - 1) c[i] = a[i] - c[i];
mul(b, c, m * 2);
}
return b;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
poly f(m + 1);
mx = up(m * 2 + 1), init(mx);
fu(i, 1, n) cin >> k, ++g[k];
fu(i, 1, m) for (int j = i, k = 1; j <= m; j += i, ++k)
f[j] = (f[j] + (ll)iv[k] * g[i]) % M;
poly g = exp(f);
g.resize(m);
fu(i, 1, m) cout << (g[i] + M) % M << '\n';
}
C.CF1436E Complicated Computations
思考如何快速判断某个MEX值x能否被构造出来,只需要遍历所有
被x分隔的区间,数据结构加速一下,如果所有小于a[i]的数最后一次
出现的位置都大于a[i]上一次出现的位置,那么MEX值a[i]可以被构造。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
#define lb(x) (x & -x)
typedef long long ll;
using namespace std;
const ll N = 1e5 + 1;
ll t, n, m, a;
ll T[N], lst[N], f[N];
void upd(ll p, ll x) {
for (; p <= n; p += lb(p)) {
T[p] = x;
for (ll i = 1; i < lb(p); i <<= 1)
mn(T[p], T[p - i]);
}
}
ll qry(ll l, ll r) {
ll res = 1e9;
while (r >= l) {
mn(res, lst[r]);
for (--r; r - lb(r) >= l; r -= lb(r))
mn(res, T[r]);
}
return res;
}
signed main() {
cin >> n;
fu(i, 1, n) {
cin >> a;
if (a ^ 1) {
f[1] = 1;
if (qry(1, a - 1) > lst[a])
f[a] = 1;
}
upd(a, i), lst[a] = i;
}
fu(i, 1, n + 1) if (!f[i] && (i == 1 || qry(1, i - 1) <= lst[i])) {
cout << i;
return 0;
}
cout << n + 2;
}
D.P2491 消防
在直径上尺取合法路径,详见[https://www.cnblogs.com/lemu/p/15510766.html]B题。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;
const ll N = 3e5 + 10;
ll n, m, u, v, w, k, e, d1, d2 = 1e9;
ll fa[N], d[N], l[N];
vector<pll> g[N];
void dfs(ll u, ll f) {
fa[u] = f, k = d[u] > d[k] ? u : k;
for (auto [v, w] : g[u])
if (v ^ f && !l[v])
d[v] = d[u] + w, dfs(v, u);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
fu(i, 2, n) {
cin >> u >> v >> w;
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}
dfs(1, 0), d[k] = 0, dfs(k, 0), e = k;
for (ll i = k, j = k; i; j = fa[j])
while (i && d[j] - d[i] <= m) {
mn(d2, max(d[e] - d[j], d[i]));
l[i] = 1, k = i, dfs(i, fa[i]);
mx(d1, d[k] - d[i]), i = fa[i];
}
cout << max(d1, d2);
}
E.P7597 猜树 加强版
暴力的做法是先得到所有节点的深度,然后从根节点开始递归询问子树。
考虑优化,选择一个大小最大的子树(重儿子),询问除它以外的所有子树,
就可以通过做差得到该子树,至于怎么选,只能想到随机,越大的子树被选择
的概率越高,复杂度我不会证。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
typedef long long ll;
using namespace std;
const int N = 5e3 + 1, M = sqrt(N - 1);
ll t, n, m, k, a, b, c, ans;
ll d[N], fa[N];
vector<ll> g[N];
inline ll q(ll x, ll y, ll z) {
cout << "? " << x << ' ';
if (x == 1)
cout << y << ' ';
cout << z << '\n';
cin >> m;
return m;
}
void dfs(ll u) {
if (!g[u].size())
return;
ll r = g[u][rand() % g[u].size()], s = 0;
for (ll v : g[u])
if (d[v] == d[u] + 1) {
fa[v] = u;
if (v == r || !s && d[r] - d[u] == q(1, v, r) + 1) {
s = v;
} else {
ll c = q(2, 0, v);
fu(i, 1, c) {
cin >> a;
if (a ^ v)
g[v].emplace_back(a);
}
dfs(v);
}
}
for (ll v : g[u])
if (!fa[v])
g[s].emplace_back(v);
dfs(s);
}
int main() {
cin >> n;
fu(i, 2, n) {
d[i] = q(1, 1, i);
g[1].emplace_back(i);
}
dfs(1);
cout << "!";
fu(i, 2, n) cout << ' ' << fa[i];
}
F.CF1436D Bandit in a City
比较容易想到一个二分贪心,因为较远的父节点拥有更多的选择,
所以叶子节点优先从距离较近的父节点得到权值。注意二分可能会爆ll,
当时济南的D题也是三分被卡int128很蛋疼。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define dbg(x) cout << #x << '=' << x << ' '
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
using ll = long long;
using LL = __int128;
const ll N = 1e6;
ll t, n, m, k, w[N];
LL x[N];
vector<ll> g[N];
ll a, ok;
void dfs(ll u = 1) {
if (!g[u].size())
x[u] -= m;
for (ll v : g[u])
dfs(v), x[u] += x[v];
if (x[u] > 0)
ok = 0;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
fu(i, 2, n) cin >> a, g[a].emplace_back(i);
fu(i, 1, n) cin >> w[i];
ll l = -1, r = 2e14;
while (l < r - 1) {
m = l + r >> 1, ok = 1;
copy(w + 1, w + n + 1, x + 1);
dfs(), ok ? r = m : l = m;
}
cout << r;
}
G.P1682 过家家
并查集处理一下得到每个女生集合对应可选的男生集合,
答案就是最小可选男生集合的大小 + k,(不超过n)。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define dbg(x) cout << #x << '=' << x << ' '
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
using ll = long long;
const ll N = 251;
ll n, m, k, f, ans;
ll x[N], y[N], fa[N], a, b;
bitset<N> g[N];
ll find(ll x) {
return fa[x] ? fa[x] = find(fa[x]) : x;
}
void u(ll x, ll y) {
ll fx = find(x), fy = find(y);
if (fx ^ fy)
fa[fx] = fy, g[fy] |= g[fx];
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m >> k >> f;
fu(i, n + 1, 2 * n) fa[i] = -1;
fu(i, 1, m) cin >> a >> b, g[a][b] = 1;
fu(i, 1, f) cin >> a >> b, u(a, b);
ans = n;
fu(i, 1, n) mn(ans, k + (ll)g[find(i)].count());
cout << ans;
}
H.P2803 学校选址 II
区间dp,dp[l][r][k]代表在区间l, r内建k所小学的最小距离和,k = 1时是个带权中位数问题。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define dbg(x) cout << #x << '=' << x << ' '
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using ll = long long;
using namespace std;
const ll N = 101;
ll n, m, k;
ll x[N], ps[N], d[N], pd[N], dp[N][N][N];
void init() { // 双指针预处理k = 1
fu(i, 1, n - 1) {
ll k = i, dis = 0; // 记录最优位置和距离
fu(j, i + 1, n) {
dis += (pd[j] - pd[k]) * x[j];
fu(k1, k + 1, j) {
ll nx_dis = dis + (ps[k1 - 1] - ps[i - 1] - (ps[j] - ps[k1 - 1])) * d[k1];
if (nx_dis > dis)
break;
dis = nx_dis, k = k1;
}
dp[i][j][1] = dis;
}
}
}
ll dfs(ll l, ll r, ll k) {
if (r - l < k) // 区间内每栋楼都有一个学校
return 0;
if (dp[l][r][k])
return dp[l][r][k];
dp[l][r][k] = 1e9;
fu(m, l, r - 1) fu(i, 1, k - 1)
mn(dp[l][r][k], dfs(l, m, i) + dfs(m + 1, r, k - i));
return dp[l][r][k];
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> k;
fu(i, 1, n) cin >> x[i], ps[i] = ps[i - 1] + x[i];
fu(i, 2, n) cin >> d[i], pd[i] = pd[i - 1] + d[i];
init();
cout << dfs(1, n, k);
}