省选测试50
A 矩形
题目大意 : 从n×m点中选k个点在一条直线上的方案数
-
莫比乌斯反演,把步骤记下来,不然老是忘(我太菜了)
-
横着和竖着的都好算,斜着的可以分为左斜和右斜,枚举这k个点所在线段所在的矩形,对于一个长宽i,j的矩形,对角线上的点数是gcd(i,j)+1,把两端确定,中间选k-2就不会重复,所以可以得到一个n2的式子(假设n<=m)
\[ans=n\binom{m}{k}+m\binom{n}{k}+2\sum_{i=1}^{n}\sum_{i=1}^{m}\binom{\gcd(i,j)-1}{k-2}(n-i)(m-j)
\]
- 对后面的两个求和的那个式子进行莫比乌斯反演
\[\begin{align*}
ans'&=\sum_{i=1}^{n}\sum_{j=1}^{m}\binom{\gcd(i,j)-1}{k-2}(n-i)(m-j)
\\&= \sum_{p=1}^{n}\binom{p-1}{k-2}\sum_{i=1}^{n}\sum_{j=1}^{m}[\gcd(i,j)=p](n-i)(m-j)
\\&= \sum_{p=1}^{n}\binom{p-1}{k-2}\sum_{i=1}^{\left \lfloor \frac{n}{p} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{p} \right \rfloor}[\gcd(i,j)=1](n-pi)(m-pj)
\\&= \sum_{p=1}^{n}\binom{p-1}{k-2}\sum_{i=1}^{\left \lfloor \frac{n}{p} \right \rfloor}\sum_{j=1}^{\left \lfloor \frac{m}{p} \right \rfloor}\sum_{d|\gcd(i,j)}\mu (d)(n-pi)(m-pj)
\\&= \sum_{p=1}^{n}\binom{p-1}{k-2}\sum_{d=1}^{\left \lfloor \frac{n}{p} \right \rfloor}\mu (d)\sum_{d|i}^{\left \lfloor \frac{n}{p} \right \rfloor}\sum_{d|j}^{\left \lfloor \frac{m}{p} \right \rfloor}(n-pi)(m-pj)
\\&= \sum_{p=1}^{n}\binom{p-1}{k-2}\sum_{d=1}^{\left \lfloor \frac{n}{p} \right \rfloor}\mu (d)\sum_{i=1}^{\left \lfloor \frac{n}{pd} \right \rfloor}(n-pdi)\sum_{j=1}^{\left \lfloor \frac{m}{pd} \right \rfloor}(m-pdj)
\end{align*}\]
-
接下来正常的话就要设T=pd,然后继续推,不过这道题到这里就可以\(O(n\ln n)\)做出来了,因为后面两个求和是显然可以用等差数列求和公式优化到O(1)
-
预处理组合数和莫比乌斯函数\(\mu (d)\),当x有相同质因子,函数值为0,否则是-1的质因子个数次方
Code
Show Code
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, M = 323232323;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
bool v[N];
int n, m, k, pri[N], tot, mu[N], fac[N], inv[N], ans;
int Pow(int a, int k, int ans = 1) {
for (; k; k >>= 1, a = 1ll * a * a % M)
if (k & 1) ans = 1ll * ans * a % M;
return ans;
}
void Init(int n) {
mu[1] = fac[0] = 1;
for (int i = 2; i < n; ++i) {
if (!v[i]) pri[++tot] = i, mu[i] = M-1;
for (int j = 1; j <= tot && i * pri[j] <= n; ++j) {
v[i*pri[j]] = 1;
if (i % pri[j] == 0) break;
mu[i*pri[j]] = M-mu[i];
}
}
for (int i = 1; i <= n; ++i)
fac[i] = 1ll * fac[i-1] * i % M;
inv[n] = Pow(fac[n], M - 2);
for (int i = n; i >= 1; --i)
inv[i-1] = 1ll * inv[i] * i % M;
}
int C(int n, int m) {
return 1ll * fac[n] * inv[m] % M * inv[n-m] % M;
}
int Cal(int n, int pd) {
return (-1ll * (n / pd) * (n / pd + 1) / 2 * pd % M + M + 1ll * n / pd * n) % M;
}
int main() {
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
n = read(); m = read(); k = read();
if (k == 1) return printf("%lld\n", 1ll * n * m % M), 0;
if (n > m) swap(n, m); Init(m);
for (int p = k-1; p <= n; ++p) {
int sum = 0;
for (int d = n / p; d >= 1; --d)
if ((sum += 1ll * Cal(n, p * d) * Cal(m, p * d) % M * mu[d] % M) >= M) sum -= M;
if ((ans += 1ll * C(p - 1, k - 2) * sum % M) >= M) ans -= M;
}
printf("%lld\n", (1ll * n * C(m, k) + 1ll * m * C(m, k) + 2 * ans) % M);
return 0;
}
B 覆盖
题目大意 : 一个点覆盖一个区间的代价是到区间两端的距离之差,问在用点最少的情况下覆盖所有线段的最小代价
-
最少用点好求,按右端点排序,每次给在第一个没选的区间的右端点放一个点
-
设p[i]表示上述贪心第i个点所放的位置,而在最小代价的情况下第i个点一定会放在[p[i-1]+1,p[i]],于是就可以dp了
-
设对于每段,f[i]表示这段放在这个点上的最小代价,转移点只在上个段
-
发现有决策单调性,可以\(n\log n\)解决
Code
Show Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
ll s[N], f[N], ans = 1e18;
int n, b[N], c[N], cnt, p[N];
struct Node {
int l, r;
}a[N];
bool operator < (const Node &a, const Node &b) {
return a.r < b.r;
}
ll Cal(int l, int r) {
if (l < b[r-1]) return 1e18;
int mid = l + r >> 1;
return (s[mid] - s[l]) - 1ll * (c[mid] - c[l]) * l + 1ll * (c[r] - c[mid]) * r - (s[r] - s[mid]) + f[l];
}
void Solve(int pl, int pr, int l, int r) {
if (pl > pr || l > r) return;
int mid = l + r >> 1, p = pl;
for (int i = pl; i <= pr; ++i) {
ll tmp = Cal(i, mid);
if (tmp < f[mid]) f[mid] = tmp, p = i;
}
Solve(pl, p, l, mid-1); Solve(p, pr, mid+1, r);
}
int main() {
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
n = read();
for (int i = 1; i <= n; ++i) {
int l = read() * 2, r = read() * 2, mid = l + r >> 1;
a[i] = (Node) {l, r}; b[r] = max(b[r], l);
s[mid] += mid; c[mid]++;
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++i)
if (a[i].l > p[cnt]) p[++cnt] = a[i].r;
memset(f + 1, 0x3f, (n *= 2) * 8);
for (int i = 1; i <= n; ++i) {
b[i] = max(b[i], b[i-1]);
s[i] += s[i-1]; c[i] += c[i-1];
}
for (int i = 1; i <= p[1]; ++i)
f[i] = 1ll * c[i] * i - s[i];
for (int i = 2; i <= cnt; ++i)
Solve(p[i-2] + 1, p[i-1], p[i-1] + 1, p[i]);
for (int i = max(p[cnt-1]+1, b[n]); i <= p[cnt]; ++i)
ans = min(ans, f[i] + (s[n] - s[i]) - 1ll * (c[n] - c[i]) * i);
printf("%d %lld\n", cnt, ans);
return 0;
}
C 强壮
题目大意 : 限制dfn序为i的点子树不小于a[i]
-
题意可真是难懂,dfn不和a[i]放一起说,气死了
-
f[x][i]表示以x为根的子树dfn序最大的链的长度为i的方案数
-
按dfn从小到大考虑可以接到i子树里的点,一定只能接在最右边的链上,
-
所以f[x][i]×f[y][j]会对f[x][j+1~i+j-1]有贡献,暴力转移的话是n3
-
可以看一个值会由谁转移来,发现f[y][j]固定后,f[x][i+j+1]只会由f[x][i~sz[x]]转移,所以做个后缀和就可以优化到n2
Code
Show Code
#include <cstdio>
#include <vector>
using namespace std;
const int N = 1e4 + 5, M = 323232323;
int read(int x = 0, int f = 1, char c = getchar()) {
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
return x * f;
}
vector<int> to[N];
int n, a[N], stk[N], tp, f[N][N], sz[N], ans, g[N];
void Dfs(int x) {
sz[x] = 1; f[x][0] = 1;
for (int k = 0, y; k < to[x].size(); ++k) {
Dfs(y = to[x][k]);
for (int i = sz[x] - 2; i >= 0; --i)
if ((f[x][i] += f[x][i+1]) >= M) f[x][i] -= M;
for (int i = 0; i < sz[x]; ++i)
for (int j = 0; j < sz[y]; ++j)
if ((g[i+j+1] += 1ll * f[x][i] * f[y][j] % M) >= M) g[i+j+1] -= M;
sz[x] += sz[y];
for (int i = 0; i < sz[x]; ++i)
f[x][i] = g[i], g[i] = 0;
}
}
int main() {
freopen("c.in", "r", stdin);
freopen("c.out", "w", stdout);
n = read(); read(); a[1] = n;
for (int i = 2; i <= n; ++i)
a[i] = i + read() - 1;
for (int i = n; i >= 1; --i) {
if (a[i] > n) return puts("0"), 0;
while (tp && stk[tp] <= a[i]) to[i].push_back(stk[tp--]);
stk[++tp] = i;
}
Dfs(1);
for (int i = 0; i < n; ++i)
if ((ans += f[1][i]) >= M) ans -= M;
printf("%d\n", ans);
return 0;
}