Problem A. Sprinklers 2: Return of the Alfalfa
若干包含右上或是左下角的矩形的并是一条单调向右、或向下的轮廓线。
考虑枚举最终两种作物的分界线,则不难发现,分界线的拐角处必须放置指定装置,其余位置可以不放置装置,也可以放置其中一种装置。因此,可以认为,轮廓线每拐一次弯,该轮廓线的贡献变为 1 2 \frac{1}{2} 21 ,从而进行动态规划计算答案。
时间复杂度 O ( N 2 ) O(N^2) O(N2) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
const int P = 1e9 + 7;
const int inv = (P + 1) / 2;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
char s[MAXN][MAXN];
int n, dp[MAXN][MAXN][2];
int main() {
freopen("sprinklers2.in", "r", stdin);
freopen("sprinklers2.out", "w", stdout);
read(n); int cnt = 0;
for (int i = 1; i <= n; i++) {
scanf("\n%s", s[i] + 1);
for (int j = 1; j <= n; j++)
cnt += s[i][j] == '.';
}
dp[1][0][0] = dp[0][1][1] = 1;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++) {
if (dp[i][j][0]) {
int tmp = dp[i][j][0];
if (i < n) update(dp[i + 1][j][0], tmp);
if (j < n && s[i][j + 1] == '.') update(dp[i][j + 1][1], 1ll * tmp * inv % P);
}
if (dp[i][j][1]) {
int tmp = dp[i][j][1];
if (j < n) update(dp[i][j + 1][1], tmp);
if (i < n && s[i + 1][j] == '.') update(dp[i + 1][j][0], 1ll * tmp * inv % P);
}
}
cout << 1ll * power(2, cnt) * (dp[n][n][0] + dp[n][n][1]) % P << endl;
return 0;
}
Problem B. Exercise
考虑枚举质数 p p p ,指数 q q q ,记 f ( x ) f(x) f(x) 表示存在一个环长是 x x x 的倍数的置换个数。
则可以认为, p q p^q pq 对答案的贡献为
× p f ( p q ) \times p^{f(p^q)} ×pf(pq)
考虑在模 φ ( M ) = M − 1 \varphi(M)=M-1 φ(M)=M−1 意义下计算 f ( x ) f(x) f(x) 。
显然有动态规划解法,单次计算时间复杂度 O ( N 2 ) O(N^2) O(N2) ,总时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool key[MAXN];
int P, fac[MAXN], binom[MAXN][MAXN];
int tot, prime[MAXN], f[MAXN];
void sieve(int n) {
fac[0] = binom[0][0] = 1;
for (int i = 1; i <= n; i++) {
binom[i][0] = 1;
fac[i] = 1ll * fac[i - 1] * i % P;
for (int j = 1; j <= i; j++)
binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
}
for (int i = 2; i <= n; i++) {
if (f[i] == 0) {
prime[++tot] = f[i] = i;
key[i] = true;
} else key[i] = key[i / f[i]] && (f[i / f[i]] == f[i]);
for (int j = 1; j <= tot && prime[j] <= f[i]; j++) {
int tmp = prime[j] * i;
if (tmp > n) break;
f[tmp] = prime[j];
}
}
}
int n, dp[MAXN][2];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % (P + 1);
else return 1ll * tmp * tmp % (P + 1) * x % (P + 1);
}
int calc(int x) {
memset(dp, 0, sizeof(dp)), dp[n][0] = 1;
for (int i = n; i >= 1; i--)
for (int j = 1; j <= i; j++) {
int coef = 1ll * binom[i - 1][j - 1] * fac[j - 1] % P;
if (j % x == 0) {
update(dp[i - j][1], 1ll * dp[i][0] * coef % P);
update(dp[i - j][1], 1ll * dp[i][1] * coef % P);
} else {
update(dp[i - j][0], 1ll * dp[i][0] * coef % P);
update(dp[i - j][1], 1ll * dp[i][1] * coef % P);
}
}
cout << dp[0][1] << endl;
return dp[0][1];
}
int main() {
freopen("exercise.in", "r", stdin);
freopen("force.out", "w", stdout);
read(n), read(P), P--;
sieve(n); int ans = 1;
for (int i = 1; i <= n; i++)
if (key[i]) ans = 1ll * ans * power(f[i], calc(i)) % (P + 1);
cout << ans << endl;
return 0;
}
观察上述动态规划的转移形式及其转移系数,可以将其优化至单次 O ( N ) O(N) O(N) 。
时间复杂度 O (