P3473 UCI - The Great Escape
简要题意: 给一个 $n\times m$ 的地图,计算从 $(n,1)$ 到第 $x$ 列的第 $y$ 行的路径条数 $\bmod k$,走过的点不能再走,转弯只能向右转。
数据规模: $n,m \le 100$。
题解: 走过的点不能再走,并且只能向右转,容易发现在此之后能够到达的点一定在某个围成的矩形内。设 dp 状态 $dp[u][d][l][r][x]$,$u,d,l,r$ 分别表示当前矩形的上下左右边界,$x$ 表示下一步的方向。初始状态从 $(x,y)$ 开始,转移时枚举往 $x$ 方向走多少步再转弯,可以做到 $O(n^5)$,具体方程如下,其中 $\mathrm{check}(k)$ 表示判断路径是否没有障碍:
$$
\begin{aligned}
dp[u][d][l][r][\text{Up}] &= \sum [\mathrm{check(k)}]dp[k][d][l+1][r][\text{Ri}] \\
dp[u][d][l][r][\text{Dn}] &= \sum [\mathrm{check(k)}]dp[u][k][l][r-1][\text{Le}] \\
dp[u][d][l][r][\text{Le}] &= \sum [\mathrm{check(k)}]dp[u][d-1][k][r][\text{Up}] \\
dp[u][d][l][r][\text{Ri}] &= \sum [\mathrm{check(k)}]dp[u+1][d][l][k][\text{Dn}] \\
\end{aligned}
$$
上述 dp 容易通过前缀和优化,以向上为例,有如下转移方程:
$$
dp[u][d][l][r][\text{Up}] = dp[u+1][d][l][r][\text{Up}] + [\mathrm{check}(u)]dp[u][d][l+1][r][\text{Ri}]
$$
于是时间复杂度就变成了 $O(n^4)$。但是空间还会炸,所以考虑滚动数组优化一下。注意到在上述转移方程中,令 $sum = d-u+1+r-l+1$,不难看出 $sum$ 总是由 $sum-1$ 转移得到,在这一维进行滚动数组即可。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| using ll = long long;
const int N = 105;
int n, m, tx, ty, mod, s1[N][N], s2[N][N], dp[2][N][N][N][4];
char mp[N][N];
enum direction { UP = 0, DN = 1, LE = 2, RI = 3 };
signed main() {
read(n), read(m), read(mod), read(ty), read(tx);
for (int i = 1; i <= n; ++i) {
scanf("%s", mp[i] + 1);
for (int j = 1; j <= m; ++j) {
s1[i][j] = s1[i][j - 1] + (mp[i][j] == '*');
s2[i][j] = s2[i - 1][j] + (mp[i][j] == '*');
}
}
int k = 0;
for (int sum = 2; sum <= n + m; ++sum) {
k ^= 1, memset(dp[k], 0, sizeof(dp[k]));
for (int lx = 1, ly = sum - lx; lx <= n; ++lx, --ly) {
if (ly < 1 || ly > m)
continue;
for (int u = 1, d = lx; d <= n; ++u, ++d) {
if (u > tx || d < tx)
continue;
for (int l = 1, r = ly; r <= m; ++l, ++r) {
if (l > ty || r < ty)
continue;
int *cur = dp[k][d][l][r];
auto get = [&](int u, int d, int l, int r, int dir) -> int {
return dp[k ^ 1][d][l][r][dir];
};
cur[UP] = get(u, d, l + 1, r, RI) + (u == tx && l == ty);
cur[UP] = cur[UP] * (s2[d][l] == s2[u - 1][l]);
cur[UP] = cur[UP] + get(u + 1, d, l, r, UP);
cur[DN] = get(u, d, l, r - 1, LE) + (d == tx && r == ty);
cur[DN] = cur[DN] * (s2[d][r] == s2[u - 1][r]);
cur[DN] = cur[DN] + get(u, d - 1, l, r, DN);
cur[LE] = get(u, d - 1, l, r, UP) + (d == tx && l == ty);
cur[LE] = cur[LE] * (s1[d][r] == s1[d][l - 1]);
cur[LE] = cur[LE] + get(u, d, l + 1, r, LE);
cur[RI] = get(u + 1, d, l, r, DN) + (u == tx && r == ty);
cur[RI] = cur[RI] * (s1[u][r] == s1[u][l - 1]);
cur[RI] = cur[RI] + get(u, d, l, r - 1, RI);
cur[UP] %= mod, cur[DN] %= mod, cur[LE] %= mod, cur[RI] %= mod;
}
}
}
}
write(dp[k][n][1][m][0]), putchar('\n');
return 0;
}
|
P3474 KUP - Plot Purchase
简要题意: 给定 $k,n$,和 $n \times n$ 的矩阵,求一个子矩阵满足权值和在 $[k,2k]$ 之间。
数据规模: $n \le 2000,\ 1\le k \le 10^9,\ 0\le a_{i,j} \le 2\times 10^9$。
题解: 神仙题。首先考虑矩阵中的每一个数 $a_{i,j}$,如果 $k \le a_{i,j} \le 2k$,直接输出即可;如果 $a_{i,j} \gt 2k$,则这个点一定不能被最终的矩形所包含。
现在矩阵中只剩下 $< k$ 的数,考虑其中一个子矩阵,其权值和为 $sum$,仍然分为 $3$ 种情况讨论。
- $sum < k$,这个子矩阵一定无法满足权值和在 $[k,2k]$ 之间。
- $k \le sum \le 2k$,直接输出这个子矩阵即可。
- $sum > 2k$,可以发现若存在这样的子矩阵,且矩阵中的所有数权值均 $< k$,则一定能切出该子矩阵的子矩阵,使得其权值和在 $[k,2k]$ 之间。下面将给出一种构造方法。
考虑子矩阵的每一行,设此行的权值和为 $s$。若 $k \le s \le 2k$,直接取出这一行;若 $s > 2k$,则一定存在该行的一个子区间,其权值和在 $[k,2k]$ 之间,取出这个子区间作为答案。因此,若存在 $s \ge k$ 的行,直接取出这一行的某个区间输出即可。
若不存在 $s \ge k$ 的行,则所有行都满足 $s < k$。此时由于 $sum > 2k$,一定能够取出若干连续的行,使得这些行的权值和在 $[k,2k]$ 之间。$\square$
综上所述,只需要找到原矩阵的任意不包含 $>2k$ 的数的子矩阵,使得其权值和 $\ge k$,就能构造出权值和在 $[k,2k]$ 中的子矩阵。因此,只需要使用悬线法或单调栈找到原矩阵剔除 $> 2k$ 的元素后的最大子矩阵,即可按照上述方法进行构造。总体时空复杂度为 $O(n^2)$。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| using ll = long long;
const int N = 2e3 + 10;
int n, k, sta[N], pre[N], nxt[N], a[N][N], up[N][N];
ll num[N], prf[N], sum[N][N];
inline ll asksum(int sx, int sy, int tx, int ty) {
return sum[tx][ty] - sum[sx - 1][ty] - sum[tx][sy - 1] + sum[sx - 1][sy - 1];
}
inline void output(int sx, int sy, int tx, int ty) {
printf("%d %d %d %d\n", sy, sx, ty, tx), exit(0);
}
signed main() {
read(k), read(n);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
read(a[i][j]);
if (k <= a[i][j] && a[i][j] <= 2 * k)
output(i, j, i, j);
up[i][j] = (a[i][j] >= k) ? 0 : up[i - 1][j] + 1;
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j];
}
ll ans = 0;
int sx = 0, sy = 0, tx = 0, ty = 0;
for (int i = 1; i <= n; ++i) {
sta[0] = 0;
for (int j = 1, tp = 0; j <= n; ++j) {
while (tp && up[i][sta[tp]] >= up[i][j])
--tp;
pre[j] = sta[tp] + 1, sta[++tp] = j;
}
sta[0] = n + 1;
for (int j = n, tp = 0; j >= 1; --j) {
while (tp && up[i][sta[tp]] >= up[i][j])
--tp;
nxt[j] = sta[tp] - 1, sta[++tp] = j;
}
for (int j = 1; j <= n; ++j) {
int ax = i - up[i][j] + 1, bx = i, ay = pre[j], by = nxt[j];
ll cur = asksum(ax, ay, bx, by);
if (cur > ans)
tie(ans, sx, sy, tx, ty) = make_tuple(cur, ax, ay, bx, by);
}
}
if (ans < k) {
return puts("NIE"), 0;
} else if (k <= ans && ans <= 2 * k) {
output(sx, sy, tx, ty);
}
for (int i = sx; i <= tx; ++i) {
for (int j = sy; j <= ty; ++j)
num[i] += a[i][j];
if (k <= num[i] && num[i] <= 2 * k) {
output(i, sy, i, ty);
} else if (num[i] > 2 * k) {
for (int j = sy; j <= ty; ++j)
prf[j] = prf[j - 1] + a[i][j];
for (int j = sy, p = sy - 1; j <= ty; ++j) {
while (prf[j] - prf[p] > 2 * k)
++p;
if (prf[j] - prf[p] >= k)
output(i, p + 1, i, j);
}
assert(0);
}
}
fill(prf + 1, prf + n + 1, 0);
for (int i = sx; i <= tx; ++i)
prf[i] = prf[i - 1] + num[i];
for (int i = sx, p = sx - 1; i <= tx; ++i) {
while (prf[i] - prf[p] > 2 * k)
++p;
if (prf[i] - prf[p] >= k)
output(p + 1, i, sy, ty);
}
return assert(0), 0;
}
|
P3477 PER - Permutation
简要题意: 给你一个序列 $s$,序列中的元素有重复,求出将这个序列的所有不同排列按字典序排列后,$s$ 的排名 $\bmod m$。
数据规模: 序列长度 $n \le 3\times 10^5,\ 1 \le s_i \le 3\times 10^5,\ 2 \le m \le 10^9$。
题解: 根据康托展开的基本式子,不难列出答案的式子如下:
$$
Ans = 1+\sum_{i=1}^{n} \frac{(n-i)!}{\prod cnt_j!} \times \sum_{k=i+1}^{n}[a_k < a_i]
$$
其中 $cnt_j$ 表示 $i$ 到 $n$ 之间 $s_k=j$ 的出现次数。
如果不是任意模数,这道题就做完了,直接用树状数组维护一下 $\sum_{k=i+1}^{n}[a_k < a_i]$ 即可。但是现在有任意模数,这意味着与 $m$ 不互质的数不存在逆元。
按照以往的套路,我们先将 $m$ 分解质因数,然后在从后往前计算 $\frac{(n-i)!}{\prod cnt_j!}$ 时,对每个质因数 $p_i$ 分别维护其次数 $k_i$,查询答案时将 $p_i^{k_i}$ 相乘。质因数的个数为 $O(\log m)$ 级别,如果用快速幂暴力求次方是 $\log^2$ 级别的复杂度,实测并不能通过。因此考虑预处理每个质因子的次方,预处理的 $k_i$ 上界是 $O(n)$ 级别的,证明只需考虑阶乘中 $p_i$ 的出现次数,即可发现上界为 $\sum_{j=1}^{+\infty}\frac{n}{p_i^j} = O(n)$。所以在筛质因子时预处理一下即可做到 $O(n \log m)$ 的优秀复杂度。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
| using ll = long long;
const int N = 3e5 + 10, K = 11;
int n, m, mod, cur, ans, a[N], cnt[N], pri[K], num[K], pws[K][N];
int qpow(int x, int y) {
int ret = 1;
for (; y; y >>= 1, x = 1ll * x * x % mod)
if (y & 1)
ret = 1ll * ret * x % mod;
return ret;
}
void exgcd(int a, int b, int &x, int &y) {
if (!b)
return x = 1, y = 0, void();
return exgcd(b, a % b, y, x), y -= a / b * x, void();
}
void getfactors(int n) {
for (int i = 2; i * i <= n; ++i) {
if (n % i != 0)
continue;
pri[++m] = i, num[m] = 0;
while (n % i == 0)
n /= i;
}
if (n != 1)
pri[++m] = n, num[m] = 0;
for (int i = 1; i <= m; ++i) {
pws[i][0] = 1;
for (int j = 1; j < N; ++j)
pws[i][j] = 1ll * pws[i][j - 1] * pri[i] % mod;
}
}
inline int getinv(int x) {
int u, v;
return exgcd(x, mod, u, v), (u + mod) % mod;
}
inline void mulx(int x) {
for (int i = 1; i <= m; ++i)
while (x % pri[i] == 0)
x /= pri[i], ++num[i];
cur = 1ll * cur * x % mod;
}
inline void divx(int x) {
for (int i = 1; i <= m; ++i)
while (x % pri[i] == 0)
x /= pri[i], --num[i];
cur = 1ll * cur * getinv(x) % mod;
}
struct binary_indexed_tree {
int bit[N];
void add(int x, int v) {
for (int i = x; i < N; i += i & -i)
bit[i] += v;
}
int sum(int x) const {
int ret = 0;
for (int i = x; i; i -= i & -i)
ret += bit[i];
return ret;
}
} bit;
inline int askx() {
int ret = cur;
for (int i = 1; i <= m; ++i)
ret = 1ll * ret * pws[i][num[i]] % mod;
return ret;
}
signed main() {
read(n), read(mod);
for (int i = 1; i <= n; ++i)
read(a[i]);
cur = ans = 1, getfactors(mod);
for (int i = n; i >= 1; --i) {
if (i != n)
mulx(n - i);
divx(++cnt[a[i]]);
ans = (ans + 1ll * bit.sum(a[i] - 1) * askx()) % mod;
bit.add(a[i], 1);
}
write(ans), putchar('\n');
return 0;
}
|
最后修改于 2025-07-05
Git 版本:
de2abab