Loading

题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

先鸽一会,不想写。

posted @ 2025-09-26 21:56  循环一号  阅读(4)  评论(0)    收藏  举报