题2
On 2025.12.16:
为了方便整理,以后题解会以单独一篇的形式放出。之后有空我将这里的题转移了。
前言:为什么不放在题 1
因为题 \(1\) 收录的有点多了,现在编辑框都卡。预计加上 code \(1300\) 行就要开新的了。
正文
AT_dp_t
如果设计状态为 \(f_{i,j}\) 表示考虑到前 \(i\) 个数第 \(i\) 个数为 \(j\) 的话将很难转移,因为这是排列,不能有重复。要是把前面的值都记下来就是暴力了。
什么东西不需要保证不重复呢又能转移呢?答案是排名,但是排名肯定不能重复吧?这里所说的是局部的排名,可以想象成动态的离散化,比如你先填了 \(1, 3, 5, 6\) 那排名为 \(1, 2, 3 ,4\),然后填了个 \(2\) 那排名就是 \(1, 3, 4, 5, 2\)。
考虑 \(f_{i, j}\) 表示前 \(i\) 个数最后一个数在这 \(i\) 个数中排名为 \(j\) 的方案数,这样的话就可以不考虑重复了。转移是显然的,前缀和优化即可。
有一个问题:为什么 \(f_{1, 1}\) 为 \(1\) 而不是 \(n\), 这是因为我们 dp 的过程中和值是无关的,最后得到的是一个全局的排名序列,显然这也是这个排列,比如最后排名为 \(5, 3, 2, 1, 4\) 那排列就是 \(5, 3, 2, 1, 4\), 所以只需要记录局部排名即可。
时间复杂度 \(\mathcal{O}(n^2)\)。
P10592
考虑弱化:删成非降不需要马上停止,这种情况下比原题多了的方案数是已经删成非降子序列还继续删的,每一个这样的序列都是从恰好一个非降序列删一步得来的。
考虑 \(f_{i, j}\) 表示以 \(i\) 结尾的长为 \(j\) 的上升子序列个数,则贡献为 \(f_{i, j} \times (n-j)!\), 多删一步的方案是 \(f_{i, j} \times (n-j)! \times j\) 的,相减即可,最后答案加上 \(n!\), 因为\(j = 1\) 时减了 \(j = 0\) 的方案。
CF547D
大牛逼图论建模。
考虑先把行列看成点,第 \(i\) 行是第 \(i\) 个点,第 \(i\) 列是第 \(n + i\) 个点,原来的点看成边,\(x_i,n+y_i\) 连边,如果是红色则 \(x_i\) 连向 \(y_i\),蓝色反之,那么发现第 \(i\) 行的蓝色点数就是第 \(i\) 个点的出度,红色点数是入度,列则相反。由于图可以分成代表行的点和代表列的点,所以是二分图。
那么要求 \(\forall i, |d^+(i)-d^-(i)| \le 1\)。
考虑弱化:如果所有点的度数都是偶数,那么原图是欧拉图,根据欧拉图性质可以分解成若干不共边的欧拉回路的并,详见 oiwiki。按照欧拉回路定向即可。
那么现在考虑本题中度数为奇数的点,显然这样的点有偶数个,因为如果有奇数个,总度数就是奇数,但是总度数等于 \(2m\) 为偶数。于是建一个虚点,度数为奇数的点连向虚点,跑欧拉回路即可。
时间复杂度 \(\mathcal{O}(n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 2e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
namespace Loop1st {
int n, d[N << 1], vis[N << 2], head[N << 2], to[N << 2], nxt[N << 2], tot = 1;
void addedge(int u, int v) {
to[++tot] = v; nxt[tot] = head[u]; head[u] = tot;
}
inline void dfs(int u) {
for (int &i = head[u]; i; i = nxt[i]) { // 这里 & 可以防止走重复边
if (!vis[i >> 1]) vis[i >> 1] = 1 + (u < N), dfs(to[i]);
}
}
void main() {
cin >> n;
for (int i = 1, x, y; i <= n; i++) {
cin >> x >> y; y += N;
addedge(x, y); addedge(y, x);
d[x]++, d[y]++;
}
for (int i = 1; i < (N << 1); i++) if (d[i] & 1) addedge(i, 0), addedge(0, i);
for (int i = 1; i < N; i++) dfs(i);
for (int i = 1; i <= n; i++) cout << (vis[i] == 1 ? "r" : "b");
cout << '\n';
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
Loop1st::main();
return 0;
}
P4484
非常好计数打表。
首先 \(f_i\) 表示考虑前 \(i\) 个的答案。注意到 \(f_{i - 1} \le f_i \le f_{i - 1} + 1\)。于是考虑差分数组 \(d\),则肯定是 \(101000\dots 10011\) 这样的形式。于是直接状压这东西,从小到大插入一个数时,其位置为 \(p\) 则 \(d_p \leftarrow 1\),其后面第一个 \(1\) 变成 \(0\),然后考虑到 \(d_1 = 1\),时间复杂度 \(\mathcal{O}(n2^{n-1})\),打表可以一分钟内跑完。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 2e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
namespace Loop1st {
const ll res[30] = {0, 1, 499122178, 2, 915057326, 540715694, 946945688, 422867403, 451091574, 317868537, 200489273, 976705134, 705376344, 662845575, 331522185, 228644314, 262819964, 686801362, 495111839, 947040129, 414835038, 696340671, 749077581, 301075008, 314644758, 102117126, 819818153, 273498600, 267588741, 2};
ll n, fac, ans, *f[2] = {new ll [1 << 27], new ll [1 << 27]};
ll inv(ll a) {
ll res = 1;
for (int b = mod - 2; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod;
return res;
}
int del(int s) { return s ? (s ^ (1 << 31 - __builtin_clz(s))) : 0;}
void GetTable() {
fac = 1; f[1][0] = 1;
cout << "1, ";
for (n = 2; n <= 28; n++) {
const int lim = (1 << (n - 2)) - 1, typ = n & 1;
fill(f[typ], f[typ] + lim + 1, 0ll);
for (int s = 0; s <= lim; s++) if (f[typ ^ 1][s]) {
const ll add = f[typ ^ 1][s];
f[typ][s] += add; while (f[typ][s] >= mod) f[typ][s] -= mod;
int l = 0, r = s;
for (int j = n - 2; ~j; j--) {
const int t = (l << 1) | (1 << j) | del(r);
f[typ][t] += add; while (f[typ][t] >= mod) f[typ][t] -= mod;
if (j && (r >> (j - 1) & 1)) l |= (1 << j - 1), r ^= (1 << j - 1);
}
}
ans = 0; fac = fac * n % mod;
for (int s = 0; s < (1 << n - 1); s++) ans = (ans + f[typ][s] * (__builtin_popcount(s) + 1)) % mod;
cout << ans * inv(fac) % mod << ", ";
}
}
void main() {
int m;
cin >> m;
cout << res[m] << '\n';
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
// Loop1st::GetTable();
Loop1st::main();
return 0;
}
LOJ6669
有意思的一道交互。
首先 \(n - 1\) 次询问求出每个点的深度,并按深度从小到大求解。这样每次其祖先都已求解完毕。
考虑对已有的树重剖,求解时,已知 \(k\) 在 \(u\) 子树中,查询 \(dis(k, bot_u)\),其中 \(bot_u\) 为 \(u\) 所在链的链底,则可以得到 \(bot_u\) 和 \(k\) 的 \(\text{lca}\) 的深度,记这个 \(\text{lca}\) 为 \(v\)。如果 \(v\) 只有一个儿子,那么 \(k\) 作为其另一个儿子,否则递归求解,时间复杂度 \(\mathcal{O}(n^2)\),询问次数 \(\mathcal{O}(n \log n)\)。
严格计算上界,其为 \(n - 1 + \sum\limits_{i = 1}^{2999} T(i)\),其中 \(n = 1\) 时 \(T(i)=1\),否则为 \(T(\lfloor\frac{n-1}{2}\rfloor)+1\)。这个上界是 \(29940\),可以通过,且不好卡到上界,因为你可以在对深度排序时用不稳定的排序,这样就卡不到了。
P6646
先鸽一会,不想写。

浙公网安备 33010602011771号