这场134过于板+水,但是2还是有质量的。所以讲题的顺序也按1342
「MCOI-02」Convex Hull 凸包
的确签到,按莫比乌斯反演的套路爆推式子就行了。不妨设 (nle m)。
(ans=sum_{k=1}^{n}d(k)sum_{l|k}mu(l)sum_{k|i}d(i)sum_{k|j}d(j)=sum_{T=1}^{n}sum_{i=1}^{[n/T]}d(i)sum_{j=1}^{[m/T]}d(j)sum_{k|T}d(k)mu(frac{T}{k}))。然后后面那个东西线性筛+埃氏筛一下就好了。其实后面那玩意衡等于 (1) 我却没有发现。。。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2000010;
template <typename T> void read(T &x) {
T f = 1;
char ch = getchar();
for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
x *= f;
}
ll prime[MAXN], mu[MAXN], d[MAXN], mi[MAXN], g[2][MAXN], h[MAXN], cnt;
bool mark[MAXN];
ll n, m, p, ans;
void sieve() {
mu[1] = d[1] = 1;
for (int i = 2; i <= m; i++) {
if (!mark[i]) {
prime[++cnt] = i;
mu[i] = -1;
d[i] = 2;
mi[i] = 1;
}
for (int j = 1; j <= cnt && prime[j] * i <= m; j++) {
mark[i * prime[j]] = 1;
if (i % prime[j] == 0) {
d[i * prime[j]] = d[i] / (mi[i] + 1) * (mi[i] + 2);
mi[i * prime[j]] = mi[i] + 1;
break;
}
mu[i * prime[j]] = -mu[i];
d[i * prime[j]] = d[i] << 1;
mi[i * prime[j]] = 1;
}
}
for (int i = 1; i <= m; i++) {
for (int j = i; j <= m; j += i) {
if (i <= n && j <= n) {
g[0][i] += d[j];
}
g[1][i] += d[j];
}
}
for (int i = 1; i <= m; i++) {
for (int j = i; j <= m; j += i) {
h[j] += d[i] * mu[j / i];
h[j] %= p;
}
}
}
int main() {
read(n); read(m); read(p);
if (n > m) swap(n, m);
sieve();
for (int i = 1; i <= n; i++) {
ans = (ans + g[0][i] * g[1][i] % p * h[i] % p + p) % p;
}
printf("%lld", ans);
return 0;
}
「MCOI-02」Ancestor 先辈
容易发现其实就是判断一个区间是不是单调上升的,只需线段树维护即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 1000010;
const ll inf = 0x7f7f7f7f7f7f7f7f;
template <typename T> void read(T &x) {
T f = 1;
char ch = getchar();
for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
x *= f;
}
struct node{
bool mark;
ll mi, mx, tag;
}tr[MAXN << 2];
ll n, m;
ll a[MAXN];
void add(ll x, ll v) {
tr[x].mi += v;
tr[x].mx += v;
tr[x].tag += v;
}
void push_down(ll x) {
if (tr[x].tag) {
add(x << 1, tr[x].tag);
add(x << 1 | 1, tr[x].tag);
tr[x].tag = 0;
}
}
void push_up(ll x) {
tr[x].mark = (tr[x << 1].mark && tr[x << 1 | 1].mark && tr[x << 1].mx <= tr[x << 1 | 1].mi);
tr[x].mi = min(tr[x << 1].mi, tr[x << 1 | 1].mi);
tr[x].mx = max(tr[x << 1].mx, tr[x << 1 | 1].mx);
}
void build(ll x, ll l, ll r) {
if (l == r) {
tr[x].mi = tr[x].mx = a[l];
tr[x].mark = true;
return;
}
ll mid = (l + r) >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
push_up(x);
}
void change(ll x, ll l, ll r, ll L, ll R, ll v) {
if (l > R || r < L) return;
if (L <= l && r <= R) {
add(x, v);
return;
}
ll mid = (l + r) >> 1;
push_down(x);
change(x << 1, l, mid, L, R, v);
change(x << 1 | 1, mid + 1, r, L, R, v);
push_up(x);
}
bool ans;
pair<ll, ll> ask(ll x, ll l, ll r, ll L, ll R) {
if (!ans) return make_pair(0, 0);
if (l > R || r < L) {
return make_pair(inf, -inf);
}
if (L <= l && r <= R) {
if (!tr[x].mark) ans = false;
return make_pair(tr[x].mi, tr[x].mx);
}
ll mid = (l + r) >> 1;
push_down(x);
pair<ll, ll> p = ask(x << 1, l, mid, L, R), q = ask(x << 1 | 1, mid + 1, r, L, R);
if (p.second > q.first) ans = false;
return make_pair(p.first, q.second);
}
int main() {
read(n); read(m);
for (ll i = 1; i <= n; i++) read(a[i]);
build(1, 1, n);
for (ll i = 1; i <= m; i++) {
ll opt, l, r, x;
read(opt); read(l); read(r);
if (opt == 1) {
read(x);
change(1, 1, n, l, r, x);
} else {
ans = true;
pair<ll, ll> tmp = ask(1, 1, n, l, r);
if (ans) puts("Yes");
else puts("No");
}
}
return 0;
}
「MCOI-02」Glass 玻璃
看到这种有线性结构的式子考虑枚举每条边的贡献。考虑以每个点为根,计算与其相连的边的贡献。我们把一条边拆成两部分计算答案,每次计算只用计算子树内的点权给的贡献,显然就是 (sum_{vin subtree(u)}sz[v]*V[v])。然后我们考虑每个点作为根的情况,直接换根dp即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 2000010;
const ll mod = 998244353;
template <typename T> void read(T &x) {
T f = 1;
char ch = getchar();
for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
x *= f;
}
struct node{
ll pre, to;
ll len;
}edge[MAXN << 1];
ll head[MAXN], tot;
ll n;
ll val[MAXN];
ll sz[MAXN], dp[MAXN];
ll ans;
void add(ll u, ll v, ll l) {
edge[++tot] = node{head[u], v, l};
head[u] = tot;
}
void dfs1(ll x, ll fa) {
sz[x] = 1;
for (ll i = head[x]; i; i = edge[i].pre) {
ll y = edge[i].to;
if (y == fa) continue;
dfs1(y, x);
sz[x] += sz[y];
dp[x] = (dp[x] + dp[y]) % mod;
}
dp[x] = (dp[x] + sz[x] * val[x]) % mod;
}
void get_ans(ll x) {
for (ll i = head[x]; i; i = edge[i].pre) {
ll y = edge[i].to;
ans = (ans + dp[y] * (n - sz[y]) % mod * edge[i].len % mod + mod) % mod;
}
}
void change_root(ll x, ll y) {
//cut
dp[x] = (dp[x] - dp[y] + mod) % mod;
dp[x] = (dp[x] - sz[x] * val[x] + mod) % mod;
sz[x] -= sz[y];
dp[x] = (dp[x] + sz[x] * val[x] + mod) % mod;
//link
dp[y] = (dp[y] + dp[x] + mod) % mod;
dp[y] = (dp[y] - sz[y] * val[y] + mod) % mod;
sz[y] += sz[x];
dp[y] = (dp[y] + sz[y] * val[y]) % mod;
}
void dfs2(ll x, ll fa) {
get_ans(x);
for (ll i = head[x]; i; i = edge[i].pre) {
ll y = edge[i].to;
if (y == fa) continue;
change_root(x, y);
dfs2(y, x);
change_root(y, x);
}
}
int main() {
read(n);
for (ll i = 1; i <= n; i++) read(val[i]);
for (ll i = 1, u, v, l; i < n; i++) {
read(u); read(v); read(l);
add(u, v, l);
add(v, u, l);
}
dfs1(1, 0);
dfs2(1, 0);
printf("%lld
", (ans << 1) % mod);
return 0;
}
「MCOI-02」Build Battle
首先容易得到一个dp,设 (f_i) 代表匹配到颜色序列的第 (i) 位的方案数,我们只用考虑前 (m) 个位置,设 (s_i) 是 (f_i) 的前缀和,有 (s_i=2*s_{i-1}-s_{i-m-1})。
然后有一个经典trick,我们尝试递归递推式,从 (0) 走到 (n),一开始有一个值 (v=1),有两种走法,一种走到 (i+1),令 (v=v*2),还有一种走到 (i+m+1),令 (v=-v)。然后对于每条到 (n) 的路径,我们记录它的权值和。于是我们发现答案之和行动方式有关,这就变成了一个计数问题。
然后就可以 (O(nln n)) 预处理了。
#include <iostream>
#include <cstdio>
using namespace std;
namespace StandardIO{
template <typename T> void read(T &x) {
T f = 1;
char ch = getchar();
for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void print(T x) {
if (x < 0) putchar('-'), x = -x;
write(x);
putchar('
');
}
}
namespace Solve{
const int mod = 1e9 + 7;
const int MAXN = 2e6 + 10;
const int MAXQ = 1e6 + 10;
static int n, q, m;
static long long ans[MAXN];
static long long fac[MAXN], ifac[MAXN], inv[MAXN];
long long ksm(long long x, long long y) {
long long ret = 1;
while (y) {
if (y & 1) ret = (ret * x) % mod;
x = (x * x) % mod;
y >>= 1;
}
return ret;
}
void init() {
fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
fac[i] = fac[i - 1] * i % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
}
long long C(long a, long b) {
if (a < 0 || b < 0 || a < b) return 0;
return fac[a] * ifac[a - b] % mod * ifac[b] % mod;
}
void MAIN() {
StandardIO :: read(n); StandardIO :: read(q);
init();
while (q--) {
StandardIO :: read(m);
if (ans[m]) {
StandardIO :: print(ans[m]);
} else {
int i = m;
for (int j = 0; j <= n / (i + 1); j++) {
ans[i] = (ans[i] + ksm(2, n - (i + 1) * j) * ksm(-1, j) * C(n - i * j, j) % mod + mod) % mod;
}
StandardIO :: print(ans[m]);
}
}
}
}
int main() {
Solve :: MAIN();
return 0;
}