[噼昂!]我被卷飛了
寫 \(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\)
其中 \(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\)
這不就是虛樹的直徑嗎?因此,我們可以給出這個 "最短路徑" 的定義:
對於一個點集 \(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;
}