不要忘了点好评!
Update #1:Task #2 题解已更新。
Update #2:Task #3 题解已更新。已经全部更新完毕。
Task #1:占领地区 Occupy
Subtask #1:$n,m\le3000\ (50\ pts)$
直接暴力对网格图进行覆盖即可。
#include <cstdio>
const int subn = 3005;
int n, m, b[subn][subn];
int main() {
scanf("%d %d", &n, &m);
for (int x, y, k = 1; k <= m; k++) {
scanf("%d %d", &x, &y);
for (int i = x, j = y; i > 0 && j > 0; i--, j--) {
b[i][j] = 1;
}
for (int i = x, j = y; i > 0 && j <= n; i--, j++) {
b[i][j] = 1;
}
for (int i = x, j = y; i <= n && j > 0; i++, j--) {
b[i][j] = 1;
}
for (int i = x, j = y; i <= n && j <= n; i++, j++) {
b[i][j] = 1;
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ans += !b[i][j];
}
}
printf("%d\n", ans);
return 0;
}
Subtask #2:$n\le100000,m\le3000\ (70\ pts)$
将两种对角线分开处理,将总答案分别减去这两种对角线的答案,再加上两种对角线重合的格子数量即可。直接计算的时间复杂度为 $\Theta(n+m^2)$。
Subtask #3:$n,m\le100000\ (100\ pts)$
如何快速计算出两种对角线重合的格子数量呢?枚举每一个 $x-y$ 为恒定值的对角线,用前缀和计算有几个 $x+y$ 为恒定值的对角线与之重合即可。时间复杂度 $\Theta(n+m)$。具体细节见代码。
#include <cstdio>
const int maxn = 100005;
const int maxm = 200005;
int n, m, bs[maxm], bd[maxm];
int abs(int x) {
return x < 0 ? -x : x;
}
int main() {
scanf("%d %d", &n, &m);
for (int x, y, i = 1; i <= m; i++) {
scanf("%d %d", &x, &y);
bs[x + y] = 1;
bd[x - y + n] = 1;
}
long long ans = 1ll * n * n;
for (int i = 2; i <= 2 * n; i++) {
if (bs[i]) {
ans -= n - abs(n + 1 - i);
}
}
for (int i = 1; i < 2 * n; i++) {
if (bd[i]) {
ans -= n - abs(n - i);
}
}
for (int i = 2; i <= 2 * n; i++) {
bs[i] += bs[i - 2];
}
for (int i = 1; i < 2 * n; i++) {
if (bd[i]) {
int x = abs(n - i);
ans += bs[2 * n - x] - bs[x];
}
}
printf("%lld\n", ans);
return 0;
}
Task #2:匹配 Match
Subtask #1:$n,m\le4\ (20\ pts)$
直接暴力枚举同学的位置即可。
Subtask #2:$n,m\le300\ (50\ pts)$
我们考虑对于每条边计算总共经过这条边的次数。我们发现对于每种配对方案,我们肯定是将一条边左边的 $男\ /\ 女$ 同学有先和右边的 $女\ /\ 男$ 同学配对。我们设这条边断开后两个联通块的大小为 $size_0$ 和 $size_1$,则经过它的方案数为:
$$\sum_{i=0}^{m}\sum_{j=0}^{m}(\min(i, m-j)+\min(m-i, j))\times C^i_mC^j_m\times size_0^i\cdot size_1^{m-i}\cdot size_0^j\cdot size_1^{m-j}$$
直接计算的复杂度为 $\Theta(nm^2)$。
Subtask #3:$n,m\le2500\ 最多只有一条边的权值为正\ (20\ pts)$
对于权值为 $0$ 的边跳过即可。结合前述算法可得 $70\ pts$。
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const int maxm = 10005;
const ll P = 1e9 + 7;
int n, m, sz[maxn];
int tot, ter[maxm], wei[maxm], nxt[maxm], lnk[maxn];
ll ans, power[maxn][maxn], inv[maxn], fact[maxn], finv[maxn];
ll C(int n, int m) {
return fact[n] * finv[m] % P * finv[n - m] % P;
}
void addedge(int u, int v, int w) {
ter[++tot] = v;
wei[tot] = w;
nxt[tot] = lnk[u];
lnk[u] = tot;
}
void dfs(int u, int p = 0) {
sz[u] = 1;
for (int v, w, e = lnk[u]; e; e = nxt[e]) {
v = ter[e], w = wei[e];
if (v == p) continue;
dfs(v, u);
sz[u] += sz[v];
if (w == 0) continue;
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= m; j++) {
ll x = w * (min(i, m - j) + min(m - i, j)) % P;
x = x * C(m, i) % P * C(m, j) % P;
x = x * power[sz[v]][i] % P * power[n - sz[v]][m - i] % P;
x = x * power[sz[v]][j] % P * power[n - sz[v]][m - j] % P;
ans = (ans + x) % P;
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
power[i][0] = 1;
for (int j = 1; j <= m; j++) {
power[i][j] = power[i][j - 1] * i % P;
}
}
inv[1] = 1;
for (int i = 2; i <= m; i++) {
inv[i] = (P - P / i) * inv[P % i] % P;
}
fact[0] = finv[0] = 1;
for (int i = 1; i <= m; i++) {
fact[i] = fact[i - 1] * i % P;
finv[i] = finv[i - 1] * inv[i] % P;
}
for (int u, v, w, i = 1; i < n; i++) {
scanf("%d %d %d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
dfs(1);
printf("%lld\n", ans);
return 0;
}
Subtask #4:$n,m\le2500\ (100\ pts)$
我们发现可以预处理出:
$$\sum_{i+j=m}(\min(i, m-j)+\min(m-i, j))\times C^i_mC^j_m$$
发现剩下的式子只和 $i+j$ 有关,然后对于每条边就可以 $\Theta(m)$ 计算了。
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const int maxm = 10005;
const ll P = 1e9 + 7;
int n, m, sz[maxn];
int tot, ter[maxm], wei[maxm], nxt[maxm], lnk[maxn];
ll ans, power[maxn][maxm], inv[maxn], fact[maxn], finv[maxn], sum[maxm];
ll C(int n, int m) {
return fact[n] * finv[m] % P * finv[n - m] % P;
}
void addedge(int u, int v, int w) {
ter[++tot] = v;
wei[tot] = w;
nxt[tot] = lnk[u];
lnk[u] = tot;
}
void dfs(int u, int p = 0) {
sz[u] = 1;
for (int v, w, e = lnk[u]; e; e = nxt[e]) {
v = ter[e], w = wei[e];
if (v == p) continue;
dfs(v, u);
sz[u] += sz[v];
if (w == 0) continue;
for (int i = 0; i <= 2 * m; i++) {
ll x = w * sum[i] % P;
x = x * power[sz[v]][i] % P * power[n - sz[v]][2 * m - i] % P;
ans = (ans + x) % P;
}
}
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
power[i][0] = 1;
for (int j = 1; j <= 2 * m; j++) {
power[i][j] = power[i][j - 1] * i % P;
}
}
inv[1] = 1;
for (int i = 2; i <= m; i++) {
inv[i] = (P - P / i) * inv[P % i] % P;
}
fact[0] = finv[0] = 1;
for (int i = 1; i <= m; i++) {
fact[i] = fact[i - 1] * i % P;
finv[i] = finv[i - 1] * inv[i] % P;
}
for (int u, v, w, i = 1; i < n; i++) {
scanf("%d %d %d", &u, &v, &w);
addedge(u, v, w);
addedge(v, u, w);
}
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= m; j++) {
sum[i + j] = (sum[i + j] + (min(i, m - j) + min(m - i, j)) * C(m, i) % P * C(m, j) % P) % P;
}
}
dfs(1);
printf("%lld\n", ans);
return 0;
}
Task #3 导数卷积 Convolution
Subtask #1:$n\le200\ (20\ pts)$
直接暴力计算即可。时间复杂度 $\Theta(n^3)$。
Subtask #2:$n\le1000\ (40\ pts)$
直接暴力计算 $f^{(i)}$ ,然后 NTT 计算卷积即可。时间复杂度 $\Theta(n^2\log_2n)$。
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 1 << 13 | 1;
const ll P = 998244353, g = 3;
int n, m, k, r[maxn];
ll a[maxn][maxn], b[maxn], c[maxn];
ll mpow(ll a, ll b) {
ll c = 1;
b = (b % (P - 1) + P - 1) % (P - 1);
for (; b; b >>= 1, a = a * a % P) {
if (b & 1) {
c = a * c % P;
}
}
return c;
}
void ntt(ll *a, int n, int type) {
for (int i = 0; i < n; i++) {
if (i < r[i]) {
swap(a[i], a[r[i]]);
}
}
for (int k = 1; k < n; k <<= 1) {
ll x = mpow(g, type * (P - 1) / (k << 1));
for (int i = 0; i < n; i += k << 1) {
ll y = 1;
for (int j = i; j < i + k; j++, y = x * y % P) {
ll p = a[j], q = y * a[j + k] % P;
a[j] = (p + q) % P, a[j + k] = (p - q + P) % P;
}
}
}
if (type == -1) {
ll x = mpow(n, P - 2);
for (int i = 0; i < n; i++) {
a[i] = x * a[i] % P;
}
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%lld", &a[0][i]);
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
a[i][j] = 1ll * (j + 1) * a[i - 1][j + 1] % P;
}
}
for (m = 1; m < n; m <<= 1) k++;
for (int i = 0; i < m; i++) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) << (k - 1));
}
for (int i = 0; i < n; i++) {
ntt(a[i], m, 1);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
b[j] = a[i][j] * a[n - i - 1][j] % P;
}
ntt(b, m, -1);
for (int j = 0; j < n; j++) {
c[j] = (b[j] + c[j]) % P;
}
}
for (int i = 0; i < n; i++) {
printf("%lld%c", c[i], " \n"[i == n - 1]);
}
return 0;
}
Subtask #3:$n\le5000\ (60\ pts)$
令 $F_i$ 为 $f(x)$ 第 $i$ 项的系数。则有:
$$G_d=\sum_{i=0}^{n-1}\sum_{0}^{d}F_{i+j}\cdot\dfrac{(i+j)!}{j!}\times F_{n-1+d-i-j}\cdot\dfrac{(n-1+d-i-j)!}{(d-j)!}$$
令 $H_i=I!\cdot F_i$,原式变为:
$$G_d=\sum_{i=0}^{n-1}\sum_{j=0}^dH_{i+j}\cdot\dfrac{1}{j!}\times H_{n-1+d-i-j}\cdot\dfrac{1}{(d-j)!}$$ $$G_d=\sum_{i=0}^{n-1+d}H_i\times H_{n-1+d-i}\cdot\sum_{0}^{d}\dfrac{1}{(d-j)!j!}$$ $$G_d=\dfrac{2^d}{d!}\cdot\sum_{i=0}^{n-1+d}H_i\times H_{n-1+d-i}$$
直接计算的复杂度为 $\Theta(n^2)$。
Subtask #4:$n\le100000\ (100\ pts)$
使用 NTT 计算上式,即可将复杂度优化到 $\Theta(n\log_2n)$。
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 100005;
const int maxm = 1 << 18 | 1;
const ll P = 998244353, g = 3;
int n, m, b, r[maxm];
ll inv[maxn], finv[maxn];
ll fact[maxn], pow2[maxn];
ll A[maxm], B[maxm];
ll power(ll a, ll b) {
ll c = 1;
b = (b % (P - 1) + P - 1) % (P - 1);
for (; b; b >>= 1, a = a * a % P) {
if (b & 1) {
c = a * c % P;
}
}
return c;
}
void prework() {
inv[1] = 1;
for (int i = 2; i < n; i++) {
inv[i] = (P - P / i) * inv[P % i] % P;
}
fact[0] = finv[0] = 1;
for (int i = 1; i < n; i++) {
fact[i] = fact[i - 1] * i % P;
finv[i] = finv[i - 1] * inv[i] % P;
}
pow2[0] = 1;
for (int i = 1; i < n; i++) {
pow2[i] = pow2[i - 1] * 2 % P;
}
for (m = 1; m < n << 1; m <<= 1) b++;
for (int i = 0; i < m; i++) {
r[i] = (r[i >> 1] >> 1) | ((i & 1) << (b - 1));
}
}
void ntt(ll *a, int n, int type) {
for (int i = 0; i < n; i++) {
if (i < r[i]) {
swap(a[i], a[r[i]]);
}
}
for (int k = 1; k < n; k <<= 1) {
ll x = power(g, type * (P - 1) / (k << 1));
for (int i = 0; i < n; i += k << 1) {
ll y = 1;
for (int j = i; j < i + k; j++, y = x * y % P) {
ll p = a[j], q = y * a[j + k] % P;
a[j] = (p + q) % P, a[j + k] = (p - q + P) % P;
}
}
}
if (type == -1) {
ll x = power(n, P - 2);
for (int i = 0; i < n; i++) {
a[i] = a[i] * x % P;
}
}
}
int main() {
scanf("%d", &n);
prework();
for (int i = 0; i < n; i++) {
scanf("%lld", &A[i]);
A[i] = B[i] = A[i] * fact[i] % P;
}
ntt(A, m, 1), ntt(B, m, 1);
for (int i = 0; i < m; i++) {
A[i] = A[i] * B[i] % P;
}
ntt(A, m, -1);
for (int i = 0; i < n; i++) {
A[i + n - 1] = A[i + n - 1] * finv[i] % P * pow2[i] % P;
}
for (int i = n - 1; i < 2 * n - 1; i++) {
printf("%lld%c", A[i], " \n"[i == 2 * n - 2]);
}
return 0;
}