1. 程式人生 > 其它 >[噼昂!]我被卷飛了

[噼昂!]我被卷飛了

\[\color{red}{\text{校長者,真神人也,左馬桶,右永神,會執利筆破邪炁,何人當之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本當に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{對曰:“無人,狗欲當之,還請賜教!”}} \\ \newcommand\bra[1]{\left({#1}\right)} \newcommand\Bra[1]{\left\{{#1}\right\}} \newcommand\dx[0]{\text{dx}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \]

  寫 \(230\) 掛成 \(30\),真的笑麻了。以後還是穩健一點。

Problem A. 按位或 / \(\mathcal{Or}\)

  先考察若沒有 "被 \(3\) 整除" 這個性質怎麼做。不難想到容斥,我們容斥至多哪些位置有 \(1\). 設 \(t\)\(1\) 的數位個數為 \(c\),那麼有下列容斥式子:

\[ans=\sum_{i=0}^c(-1)^{c-i}\bra{{c\choose i}2^c}^n \]

  接下來我們應當考察被 \(3\) 整除的性質:用 \(10\) 進位制下被三整除的性質類比,不難發現:

\[2^{2k}\equiv 1\pmod 3\qquad2^{2k+1}\equiv -1\pmod 3 \]

  顯然,此時一個數在二進位制下,奇偶數位的地位是不等價的,因此我們把這兩維分開考慮做容斥即可。設 \(t\)

的偶數位有 \(even\)\(1\),奇數位有 \(odd\)\(1\),那麼最後的容斥就是:

\[ans=\sum_{i=0}^{odd}\sum_{j=0}^{even}(-1)^{odd+even-i-j}f^n(i,j) \]

  其中 \(f(i,j)\) 表示奇數位至多有 \(i\) 個,偶數位至多有 \(j\) 個的數字個數:

\[f(x,y)=\sum_{i=0}^x\sum_{j=0}^y[2i+j\equiv 0\pmod 3]{x\choose i}{y\choose j} \]

  時間複雜度 \(\mathcal O(\log^4t)\).

#include <bits/stdc++.h>
using namespace std;

namespace Elaina {

#define rep(i, l, r) for (int i = l, i##_end_ = r; i <= i##_end_; ++i)
#define drep(i, l, r) for (int i = l, i##_end_ = r; i >= i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')

    template<class T> inline T fab(T x) { return x < 0? -x: x; }
    template<class T> inline void getmax(T& x, const T& rhs) { x = max(x, rhs); }
    template<class T> inline void getmin(T& x, const T& rhs) { x = min(x, rhs); }
    
    using ll = long long;
    using ull = unsigned long long;
    using pii = pair<int, int>;

} // namespace Elaina
using namespace Elaina;

const int mod = 998244353;
const int logt = 60;
inline void Add(int& x, const int& rhs) { (x += rhs) >= mod? x -= mod: 0; }
inline int qkpow(int a, ll n) {
    int ret = 1;
    for (; n; n >>= 1, a = 1ll * a * a % mod)
        if (n & 1) ret = 1ll * ret * a % mod;
    return ret;
}

ll n, t;
int C[105][105], ecnt, ocnt;
inline void prelude() {
    C[0][0] = 1;
    for (int i = 1; i <= 100; ++i) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; ++j) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
}

inline int calc(int x, int y) {
    int ret = 0;
    for (int i = 0; i <= x; ++i) for (int j = 0; j <= y; ++j) {
        if (((i << 1) + j) % 3 == 0)
            Add(ret, 1ll * C[x][i] * C[y][j] % mod);
    }
    return ret;
}

signed main() {
    freopen("or.in", "r", stdin);
    freopen("or.out", "w", stdout);
    cin.tie(NULL) -> sync_with_stdio(false);
    prelude(); cin >> n >> t;
    for (int i = 0; i <= logt; ++i) if (t >> i & 1) (i & 1)? ++ocnt: ++ecnt;
    int ans = 0;
    for (int i = 0; i <= ocnt; ++i) for (int j = 0; j <= ecnt; ++j) {
        if((ocnt + ecnt - i - j) & 1)
            Add(ans, mod - 1ll * C[ocnt][i] * C[ecnt][j] % mod * qkpow(calc(i, j), n) % mod);
        else Add(ans, 1ll * C[ocnt][i] * C[ecnt][j] % mod * qkpow(calc(i, j), n) % mod);
    }
    printf("%d\n", ans % mod);
    return 0;
}

Problem B. 最短路徑 / \(\mathcal {Tree}\)

  先不管期望,最後我們只需要乘上 \({m\choose k}^{-1}\pmod{p}\) 即可。那麼我們現在的問題是:求從 \(m\) 個點中任意選擇 \(k\) 個點,找到一條經過這 \(k\) 個點的最短路徑,所有方案的最短路徑的長度之和。

  我們應當先考察經過 \(k\) 個點的所謂 "最短路徑" 的組成 —— 如果我們連要求什麼都不知道,還做不做了。顯然,原樹上有一些邊貢獻了 \(2\) 次,有一些邊 \(1\) 次,還有一些邊根本沒有貢獻。而沒有貢獻的這些邊身份很清楚 —— 他們沒有在 \(k\) 個點構成的虛樹上,那麼出現一次和兩次又是什麼情況?我們不妨先走出一條從 \(u\)

開始,最後又回到 \(u\) 的路徑(或者說環),那麼,所有在虛樹上的邊都出現了兩次,不過,顯然我們最後是沒有必要回到 \(u\) 的,我們可以去掉一條從 \(u\) 開始到某個點結束的路徑,顯然我們希望找最長的,這樣總路徑長可能最短。同理,這個環的端點不一定是 \(u\),可能是其他的點,因此,我們去掉的長度是:

\[\max_{u,v\in P} dis(u,v) \]

  這不就是虛樹的直徑嗎?因此,我們可以給出這個 "最短路徑" 的定義:

  對於一個點集 \(P(|P|=k)\),其最短路徑的長度 \(L(P)\) 定義為:

\[L(P)=\sum_{u\in P}\sum_{v\in P}dis(u,v)-D(P) \]

  其中 \(D(P)=\max_{u,v\in P} dis(u,v)\),即 \(P\) 構成的虛樹的直徑。

  我們已經弄清楚我們想要求什麼了,接下來就是怎麼求這個東西。顯然,如果你要直接維護似乎有點困難(至少得三維,維數就已經超過限制了),但是我們發現要減去的東西似乎是獨立的,我們可以先把前面那一坨 —— 指帶倆 \(\Sigma\) 的東西 —— 給算出來,然後減去後面的那一坨 \(D(P)\).

  前面那一坨就不說了,對於後面那一坨,我們可以列舉直徑到底是哪倆點之間的路徑,然後統計這一路徑在哪些點集被作為直徑。具體操作,我們可以列舉一個點對 \((u,v)\),然後一個點一個點地加入點集,加入時判斷這個點和左右兩點之間的距離即可,注意,若距離相同,則你需要堆點集也欽定一個偏序關係,以保證同一直徑不會被反覆計算。

#include <bits/stdc++.h>
using namespace std;

namespace Elaina {

#define rep(i, l, r) for (int i = l, i##_end_ = r; i <= i##_end_; ++i)
#define drep(i, l, r) for (int i = l, i##_end_ = r; i >= i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')

    template<class T> inline T fab(T x) { return x < 0? -x: x; }
    template<class T> inline T getmax(T x, const T& rhs) { x = max(x, rhs); }
    template<class T> inline T getmin(T x, const T& rhs) { x = min(x, rhs); }

    using ll = long long;

} // namespace Elaina
using namespace Elaina;

const int mod = 998244353;
const int maxn = 2000;
const int maxm = 300;
inline int qkpow(int a, int n) {
    int ret = 1;
    for (; n; n >>= 1, a = 1ll * a * a % mod)
        if (n & 1) ret = 1ll * ret * a % mod;
    return ret;
}
int fac[maxn + 5], ifac[maxn + 5], inv[maxn + 5];
inline void prelude() {
    fac[0] = fac[1] = ifac[0] = ifac[1] = inv[0] = inv[1] = 1;
    for (int i = 2; i <= maxn; ++i) {
        fac[i] = 1ll * fac[i - 1] * i % mod;
        inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
        ifac[i] = 1ll * ifac[i - 1] * inv[i] % mod;
    }
}
inline int C(int n, int m) {
    if (n < m || n < 0 || m < 0) return 0;
    return 1ll * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}

int p[maxm + 5], n, m, K;
int id[maxn + 5];
bool iskey[maxn + 5];
vector<int> g[maxn + 5];

inline void input() {
    cin >> n >> m >> K;
    rep (i, 1, m) cin >> p[i], iskey[p[i]] = true, id[p[i]] = i;
    int u, v;
    rep (i, 1, n - 1) {
        cin >> u >> v;
        g[u].push_back(v), g[v].push_back(u);
    }
}

int dis[maxm + 5][maxn + 5];
void dfs(int u, int par, int rt, int d) {
    dis[rt][u] = d;
    for (int v: g[u]) if(v ^ par)
        dfs(v, u, rt, d + 1);
}

int cnt[maxn + 5];
void dfs2(int u, int par) {
    cnt[u] = iskey[u];
    for (int v: g[u]) if (v ^ par)
        dfs2(v, u), cnt[u] += cnt[v];
}

inline void calc() {
    int sum = 0; /** calculate the edge which would not be considered into the answer */
    rep (i, 1, m) rep (j, i + 1, m) {
        int qualified = 0, d = dis[i][p[j]];
        rep (k, 1, n) if (k != p[i] && k != p[j]) {
            if (!iskey[k]) continue;
            if (dis[i][k] > d || dis[j][k] > d) continue;
            if (dis[i][k] < d && dis[j][k] < d) ++qualified;
            else if (dis[i][k] == d) {
                // to avoid the same situation
                if (id[k] > j) ++qualified;
            }
            else if(dis[j][k] == d) {
                if (id[k] > i) ++qualified;
            }
        }
        sum = (sum + 1ll * C(qualified, K - 2) * d % mod) % mod;
    }
    int all = 0; dfs2(1, 0);
    for (int i = 2; i <= n; ++i)
        all = (0ll + all + C(m, K) + mod - C(cnt[i], K) + mod - C(m - cnt[i], K)) % mod;
    all = ((ll)all << 1) % mod;
    all = (0ll + all + mod - sum) % mod;
    all = 1ll * all * qkpow(C(m, K), mod - 2) % mod;
    printf("%d\n", all);
}

signed main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    prelude();
    input();
    rep (rt, 1, m) dfs(p[rt], 0, rt, 0);
    calc();
    return 0;
}

Problem C. 仙人掌 / \(\mathcal{Cactus}\)

  著實妙啊。考察行列式的性質,我們最後為每個點指定一條出邊,最後整張圖的構成只有可能有兩種:兩兩匹配,或者是一個大環。如果是兩兩匹配,那麼它對答案的貢獻是乘上一個 \(-1\),如果是一個大環,那麼它對答案的貢獻就是 \((-1)^{siz-1}2\),其中 \(siz\) 表示這個環的大小,有 \(2\) 是因為大環可能存在兩個方向。然後,我們就可以用這個東西來 \(\rm DP\),具體的 \(\rm DP\) 過程,可以考慮建出圓方樹,對於每個點定義 \(f(i,0|1)\) 表示該點是否有匹配覆蓋的所有方案的和,對於方點,它的定義是其父親是否和環中的某個點匹配的所有方案之和。然後做 \(\rm DP\) 就行了,複雜度 \(\mathcal O(n)\). 不得不說,如果你對行列式有足夠掌握,並且熟悉圓方樹一類的東西,那麼它就是圓方樹 \(\rm DP\) 的板題。

#include <bits/stdc++.h>
using namespace std;

namespace Elaina {

#define rep(i, l, r) for (int i = l, i##_end_ = r; i <= i##_end_; ++i)
#define drep(i, l, r) for (int i = l, i##_end_ = r; i >= i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')

    template<class T> inline T fab(T x) { return x < 0? -x: x; }
    template<class T> inline T getmax(T x, const T& rhs) { x = max(x, rhs); }
    template<class T> inline T getmin(T x, const T& rhs) { x = min(x, rhs); }

    using ll = long long;

} // namespace Elaina
using namespace Elaina;

/**
 * @param MOD used for modulo
 * @param RT the primitive root of @p MOD
*/
template<int MOD, int RT> struct Mint {
    int val;
    static const int mod = MOD;
    Mint(ll v = 0) { val = int(-mod < v && v < mod? v: v % mod); if (val < 0) val += mod; }
    inline friend bool operator == (const Mint& a, const Mint& b) { return a.val == b.val; }
    inline friend bool operator != (const Mint& a, const Mint& b) { return !(a == b); }
    inline friend bool operator < (const Mint& a, const Mint& b) { return a.val < b.val; }
    inline friend bool operator > (const Mint& a, const Mint& b) { return a.val > b.val; }
    inline friend bool operator <= (const Mint& a, const Mint& b) { return a.val <= b.val; }
    inline friend bool operator >= (const Mint& a, const Mint& b) { return a.val >= b.val; }
    inline Mint& operator += (const Mint& rhs) { return (*this) = Mint((*this).val + rhs.val); }
    inline Mint& operator -= (const Mint& rhs) { return (*this) = Mint((*this).val - rhs.val); }
    inline Mint& operator *= (const Mint& rhs) { return (*this) = Mint(1ll * (*this).val * rhs.val); }
    inline Mint operator - () const { return Mint(-val); }
    inline Mint& operator ++ () { return (*this) = (*this) + 1; }
    inline Mint& operator -- () { return (*this) = (*this) - 1; }
    inline friend Mint operator + (Mint a, const Mint& b) { return a += b; }
    inline friend Mint operator - (Mint a, const Mint& b) { return a -= b; }
    inline friend Mint operator * (Mint a, const Mint& b) { return a *= b; }
    inline friend Mint qkpow(Mint a, ll n) {
        assert(n >= 0); Mint ret = 1;
        for (; n; n >>= 1, a *= a) if (n & 1) ret *= a;
        return ret;
    }
    inline friend Mint inverse(Mint a) { assert(a != 0); return qkpow(a, mod-2); }
};
using mint = Mint<993244853, 5>;

const int maxn = 1e5;

int n, m;
struct edge { int to, nxt; } e[maxn * 4 + 5];
int tail[maxn + 5], ecnt;
inline void add_edge(int u, int v) {
    e[ecnt] = edge{v, tail[u]}; tail[u] = ecnt++;
    e[ecnt] = edge{u, tail[v]}; tail[v] = ecnt++;
}

inline void input() {
    cin >> n >> m;
    memset(tail + 1, -1, n << 2);
    int u, v;
    rep (i, 1, m) {
        cin >> u >> v;
        add_edge(u, v);
    }
}

int dfn[maxn + 5], times, ncnt;
int fa[maxn + 5];
bool onCir[maxn + 5];
vector<int> g[maxn * 2 + 5];
void tarjan(int u, int par) {
    dfn[u] = ++times, fa[u] = par;
    for (int i = tail[u], v; ~i; i = e[i].nxt) if ((v = e[i].to) ^ par) {
        if (!dfn[v]) tarjan(v, u);
        else if (dfn[v] < dfn[u]) {
            int x = ++ncnt, cur = u;
            g[v].push_back(x);
            while (cur ^ v) {
                g[x].push_back(cur);
                onCir[cur] = true;
                cur = fa[cur];
            }
            
        }
    }
    if (!onCir[u]) g[fa[u]].push_back(u);
}
inline void buildtre() {
    ncnt = n;
    tarjan(1, 0);
}

mint f[maxn * 2 + 5][2];
inline void work(int x) {
    static mint dp[maxn * 2 + 5][2];
    int n = g[x].size();
    /** solve @b f[x][1] first */
    dp[0][0] = f[g[x][0]][0], dp[0][1] = f[g[x][0]][1];
    for (int i = 1; i < n; ++i) {
        int cur = g[x][i];
        dp[i][0] = dp[i - 1][0] * f[cur][0] - dp[i - 1][1] * f[cur][1];
        dp[i][1] = dp[i - 1][0] * f[cur][1];
    }
    f[x][1] = dp[n - 1][0];
    /** When there exists a match between @p x 's father & the last son */
    rep (_, 0, 1) {
        reverse(g[x].begin(), g[x].end());
        dp[0][0] = f[g[x][0]][0], dp[0][1] = f[g[x][0]][1];
        for (int i = 1; i < n; ++i) {
            int cur = g[x][i];
            dp[i][0] = dp[i - 1][0] * f[cur][0] - dp[i - 1][1] * f[cur][1];
            dp[i][1] = dp[i - 1][0] * f[cur][1];
        }
        f[x][0] -= dp[n - 1][1];
    }
    mint prod = 1;
    for (int v: g[x]) prod *= f[v][1];
    prod = 2 * prod * ((n & 1)? -1: 1);
    f[x][0] += prod;
}
void dfs(int u) {
    for (int v: g[u]) dfs(v);
    if (u <= n) { // on tree.
        f[u][1] = 1;
        for (int v: g[u]) {
            if (v <= n) { // simple tree edge.
                f[u][0] = f[u][0] * f[v][0] - f[u][1] * f[v][1];
                f[u][1] *= f[v][0];
            }
            else { // a square node.
                f[u][0] = f[u][0] * f[v][1] + f[u][1] * f[v][0];
                f[u][1] = f[u][1] * f[v][1];
            }
        }
    } else work(u);
}

signed main() {
    // freopen("cactus.in", "r", stdin);
    // freopen("cactus.out", "w", stdout);
    cin.tie(NULL) -> sync_with_stdio(false);
    input(); buildtre(); dfs(1);
    printf("%d\n", f[1][0].val);
    return 0;
}