[考試總結]ZROI-21-NOIP衝刺-TEST4 總結
#T1 序列變換
Time Limit: 1s Memory: 256MiB
#題意簡述
給定兩個長度為 \(n(n\leq3\times10^5)\) 的序列 \(a_i,b_i(1\leq a_i,b_i\leq10^9)\),定義兩個序列的距離為:
\[d(a,b)=\sum\limits_{i=1}^n(a_i-b_i)^2 \]你可以任意選擇 \(a_i\) 中兩個數交換位置,求最小代價,對 \(998244353\) 取模,\(T=1\) 時給出最小代價所需步數。
#大體思路
\[\begin{aligned} d(a,b)&=\sum\limits_{i=1}^n(a_i-b_i)^2=\sum\limits_{i=1}^n(a_i^2-2a_ib_i+b_i^2)\\ &=\sum\limits_{i=1}^na_i^2+\sum\limits_{i=1}^nb_i^2-2\sum\limits_{i=1}^na_ib_i, \end{aligned} \]於是我們需要做的就是讓 \(\sum_{i=1}^na_ib_i\)
我們可以得到變換後 \(a_i\) 對應的 \(b_i\) 的位置,他們會形成 \(k\) 個環,顯然我們的最優交換策略是按環交換,每個環需要交換 \(k-1\) 次,於是總交換次數是 \(n-k\) 次。
時間複雜度 \(O(n\log n)\).
#Code
#define ll long long const int N = 300010; const ll MOD = 998244353; const int INF = 0x3fffffff; template <typename T> inline void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } int n, T; ll ans, a[N], b[N], ta[N], tb[N], tot; ll sma, smb, sum, posa[N], posb[N], vis[N], vpos[N]; #define lb(t, len, x) lower_bound(t + 1, t + len + 1, x) int main() { read(n); read(T); for (int i = 1; i <= n; ++ i) read(a[i]), (sma += a[i] * a[i] % MOD) %= MOD, ta[i] = a[i]; for (int i = 1; i <= n; ++ i) read(b[i]), (smb += b[i] * b[i] % MOD) %= MOD, tb[i] = b[i]; sort(ta + 1, ta + n + 1); sort(tb + 1, tb + n + 1); for (int i = 1; i <= n; ++ i) (sum += ta[i] * tb[i] % MOD) %= MOD; ans = ((sma + smb) % MOD - 2 * sum % MOD + MOD) % MOD; printf("%lld ", ans); if (!T) return 0; for (int i = 1; i <= n; ++ i) posa[i] = lb(ta, n, a[i]) - ta; for (int i = 1; i <= n; ++ i) posb[i] = lb(tb, n, b[i]) - tb; for (int i = 1; i <= n; ++ i) vpos[posb[i]] = i; for (int i = 1; i <= n; ++ i) posa[i] = vpos[posa[i]]; for (int i = 1; i <= n; ++ i) { if (vis[i]) continue; int now = i; ++ tot; while (!vis[now]) {vis[now] = 1, now = posa[now];} } printf("%lld", n - tot); return 0; }
#T2 下象棋
Time Limit: 3s Memory Limit: 256MiB
#題意簡述
一個 \(n\times n(n\leq10^6)\) 的棋盤,上面放置了 \(m(m\leq10^6)\) 個棋子,每個棋子的個攻擊範圍為所在的兩條斜率為 \(1\) 和 \(-1\) 的斜線,問當前棋盤上有多少點不會被攻擊到。
#大體思路
一個棋子對應了斜率為 \(1\) 和 \(-1\) 的兩條斜線,這樣的斜線的個數是 \(O(n)\) 級別的,每個斜率為 \(-1\) 的斜線都與一個奇偶性確定的斜率為 \(1\) 的斜線區間相交,於是我們可以對斜率為 \(-1\) 的直線進行標記,斜率為 \(1\)
#Code
#define ll long long
const int N = 1000005;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}
template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}
bool zx[N << 1], fx[N << 1];
ll even[N << 1], odd[N << 1], n, m, ans;
inline ll len(ll x) {return Min(x, 2 * n - x);}
int main() {
read(n), read(m); ans = n * n;
for (int i = 1; i <= m; ++ i) {
ll x, y; read(x), read(y);
zx[y - x + n] = 1, fx[2 * n - x - y + 1] = 1;
}
for (int i = 1; i < n << 1; ++ i)
if (i & 1) odd[i] = odd[i - 1] + zx[i];
else odd[i] = odd[i - 1];
for (int i = 1; i < n << 1; ++ i)
if (!(i & 1)) even[i] = even[i - 1] + zx[i];
else even[i] = even[i - 1];
for (int i = 1; i < n << 1; ++ i) ans -= zx[i] * len(i);
for (int i = 1; i < n << 1; ++ i) ans -= fx[i] * len(i);
for (int i = 1; i < n << 1; ++ i) {
if (!fx[i]) continue;
if (n & 1) {
if (i & 1) ans += odd[n + len(i) - 1] - odd[n - len(i)];
else ans += even[n + len(i) - 1] - even[n - len(i)];
} else {
if (!(i & 1)) ans += odd[n + len(i) - 1] - odd[n - len(i)];
else ans += even[n + len(i) - 1] - even[n - len(i)];
}
}
printf("%lld", ans); return 0;
}
#T3 外星病毒
Time Limit: 1s Memory Limit: 256MiB
#題意簡述
一張 \(n(n\leq10^5)\) 個點 \(n\) 條邊的有向圖,每個點 \(i\) 有 \(p_i\) 的概率變為黑點,同時在變為黑點後有 \(q_i\) 的概率將自己的出邊所指向的點變為黑點,問每個點變為黑點的概率,對 \(998244353\) 取模。
#大體思路
顯然這是一個基環森林,且每個基環樹都是內向樹,考慮基環樹上的 DP;
先不考慮環的影響,對於一棵樹,設第 \(i\) 個點被染成黑色的概率是
\[f_i=p_i+(1-p_i)(1-\prod\limits_{j\in ch_i}(1-f_jq_j)), \]其中 \(\prod_{j\in ch_i}(1-f_jq_j)\) 是子樹裡一個也沒傳上來的概率,\((1-\prod_{j\in ch_j}(1-f_jq_j))\) 則是存在至少一個被染黑並傳上來的概率。
現在再來考慮在環上的點被染黑的概率,環上某個點的 \(f\) 就是它被環外的點感染的概率,\(q_i\) 表示 \(i\) 到 \(i+1\) 的有向邊存在的概率,設 \(g_i\) 表示 \(i\) 在環上被染黑的概率,先經典操作斷環為鏈。
考慮邊的存在情況,設 \(i\) 前面第一條不存在的邊是 \(j-1\) 到 \(j\) 的邊,在這種情況下,\(j\) 到 \(i-1\) 中只要有一個被染黑,\(i\) 就會被染黑,於是這部分的貢獻是
\[\sum\limits_{j=i-n+1}^i\left(\left(1-\prod_{k=j}^{i}(1-f_k)\right)\left(\prod\limits_{k=j}^{i-1}q_k\right)(1-q_{j-1})\right) \]最初不理解這裡為什麼是 \(\prod_{k=j}^{i-1}q_k\),即明明最前面的點可能沒有被染黑,為什麼需要所有邊都存在,這樣不會導致得到的概率改變麼?實際上因為我們枚舉了前面所有邊的存在性,對於一個點 \(k\) 作為最左邊黑色的點,它前面的第一條被斷開的邊的概率被分開考慮了,也就是說我們把狀態細化了,根據全概率公式,最終加起來得到的概率依然是正確的概率。不過注意到,我們這樣依舊會缺少狀態,即前面沒有邊被斷開,這也就是為什麼要考慮下面的式子。下面式子中的 \(\prod_{k=i-n+1}^{i-1}q_k\) 也是同樣的原因。
如果環上的邊全部存在的話,那麼只要有任意一個點被染黑,點 \(i\) 一定會被染黑,於是有貢獻
\[\left(1-\prod\limits_{k=i-n+1}^i(1-f_k)\right)\left(\prod\limits_{k=i-n+1}^{i-1}q_k\right) \]通過一系列過程,可以得到環上的一個點 \(i\) 的完整的概率。於是只需要考慮 \(O(1)\) 維護 \(\sum_{j=i-n+1}^i\left(\prod_{k=j}^i(1-f_k)\right)\) 和 \(\sum_{j=i-n+1}^i\left((\prod_{k=j}^{i-1}q_k)(1-q_{j-1})\right)\) 即可。
以上是題解的做法,下面是我賀的神虎的做法。
考慮對於環上的一個點,顯然當環上沒有一個點被染黑時這個點一定不會被染黑,而這種情況的概率是 \(\prod_{i=1}^n(1-f_i)\),於是除去這種情況的總概率是
\[1-\prod\limits_{i=1}^n(1-f_i) \]對於點 \(i\),我們考慮列舉最靠近它的黑點,這個總概率是
\[\sum\limits_{j=i-n+1}^{i-1}\left(f_j\cdot\prod\limits_{k=j+1}^{i}(1-f_k)\right)+f_i, \]但是在這所有的情況中,顯然只有最近的點 \(j\) 到 \(i\) 的路徑上的所有邊都存在時才有可能將 \(i\) 染為黑色,於是這種情況的概率是
\[\sum\limits_{j=i-n+1}^{i-1}\left(f_jq_j\cdot\prod\limits_{k=j+1}^{i}q_{k-1}(1-f_k)\right)+f_i, \]於是對於點 \(i\) 的答案就是
\[1-\sum\limits_{j=i-n+1}^{i-1}\left(f_j\cdot\prod\limits_{k=j+1}^{i}(1-f_k)\right)-f_i+\sum\limits_{j=i-n+1}^{i-1}\left(f_jq_j\cdot\prod\limits_{k=j+1}^{i}q_{k-1}(1-f_k)\right)+f_i-\prod\limits_{i=1}^n(1-f_i) \]本來賀寫到這就結束了,但是突然意識到
就是環上的答案,不需要額外多算其他的部分。
#Code
#define ll long long
const int N = 100010;
const ll MOD = 998244353;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, fa[N], t[N], vis[N], lst[N], inc[N];
ll p[N], q[N], f[N], part1[N], g[N], m[N];
vector <int> trees[N];
inline ll fpow(ll x, ll y) {
ll res = 1;
while (y) {
if (y & 1) (res *= x) %= MOD;
y >>= 1, (x *= x) %= MOD;
}
return res;
}
inline int find(int x) {while (x != fa[x]) x = fa[x] = fa[fa[x]]; return x;}
int que[N], frt, tal;
void solve(vector <int> &tree) {
/*Get the ring on the tree.*/
int now = tree[0];
while (!vis[now]) vis[now] = true, now = t[now];
vector <int> ring(0); int nnow = now;
do {
ring.push_back(nnow);
inc[nnow] = true; nnow = t[nnow];
} while (nnow != now);
for (int i = 0; i < ring.size(); ++ i) {
int j = (i + 1) % ring.size();
lst[ring[j]] = ring[i];
}
/*Use queue to run DP on the tree.*/
for (auto u : tree) vis[u] = false;
for (auto u : tree) ++ vis[t[u]];
frt = 0; tal = -1; for (auto u : tree) f[u] = 1;
for (auto u : tree) if (!vis[u]) que[++ tal] = u;
while (frt <= tal) {
int x = que[frt ++];
if (!inc[t[x]] && !(-- vis[t[x]])) que[++ tal] = t[x];
f[x] = f[x] * (MOD + 1 - p[x]) % MOD;
f[x] = (MOD + 1 - f[x]) % MOD;
(f[t[x]] *= (MOD + 1 - q[x] * f[x] % MOD)) %= MOD;
}
for (auto u : ring) {
f[u] = f[u] * (MOD + 1 - p[u]) % MOD;
(f[u] = MOD + 1 - f[u]) %= MOD;
}
/*Calculate the answers on the ring.*/
ll M = 1;
for (auto u : ring) {
m[u] = (MOD + 1 - f[u]) * q[lst[u]] % MOD,
(M *= m[u]) %= MOD;
}
now = ring[0]; ll ans0 = 0, tim = 1;
do {
(ans0 += tim * f[now]) %= MOD;
tim = tim * m[now] % MOD; now = lst[now];
} while(now != ring[0]);
for (auto u : ring) {
g[u] = ans0; ans0 = ans0 * m[t[u]] % MOD;
ans0 = ans0 - M * f[t[u]] + f[t[u]];
ans0 = ((ans0 % MOD) + MOD) % MOD;
}
for (auto u : ring) f[u] = g[u];
}
int main() {
read(n);
for (int i = 1; i <= n; ++ i) fa[i] = i;
for (int i = 1, a, b; i <= n; ++ i) {
read(a), read(b);
p[i] = 1ll * a * fpow(b, MOD - 2) % MOD;
}
for (int i = 1; i <= n; ++ i) read(t[i]);
for (int i = 1, a, b; i <= n; ++ i) {
read(a), read(b);
q[i] = 1ll * a * fpow(b, MOD - 2) % MOD;
}
for (int i = 1; i <= n; ++ i)
if (find(i) != find(t[i]))
fa[fa[i]] = fa[t[i]];
for (int i = 1; i <= n; ++ i)
trees[find(i)].push_back(i);
for (int i = 1; i <= n; ++ i)
if (trees[i].size()) solve(trees[i]);
for (int i = 1; i <= n; ++ i)
printf("%lld ", f[i]);
return 0;
}
#T4 刪邊方案
Time Limit: 5s Memory Limit: 256MiB
#題意簡述
給定一張 \(n(\leq18)\) 個點 \(m(m\leq n(n-1))\) 條邊的有向圖,問有多少種不同的刪邊方案得到的圖依舊存在環。
#大體思路
直接計算答案比較困難,考慮補集轉化,我們求刪邊後得到 DAG(有向無環圖)的方案數。
設 \(f_S\) 表示點集 \(S\) 刪邊得到 DAG 的方案數,直接計算依舊存在難度,考慮容斥。設 \(q_T\) 為我們令 \(S\) 的子集 \(T\) 中的點的入度都是 \(0\) ,\(S\) 中可能存在其他入度為 \(0\) 的點的方案數,考慮到 \(T\to S-T\) 的邊可以連也可以不連, \(S-T\to T\) 的邊一定不連,於是有:
\[q_T=2^{ways(T,S-T)}\times f_{S-T}, \]我們設 \(p_T\) 為僅有 \(T(T\subseteq S)\) 中的點是入度為 \(0\) 的點時的方案數,顯然有:
\[q_T=\sum\limits_{T\subseteq R\subseteq S}p_R \]於是根據二項式反演的集合形式有
\[p_T=\sum\limits_{T\subseteq R\subseteq S}(-1)^{|R|-|T|}q_R, \]於是我們所求的答案便是
\[\begin{aligned} f_S&=\sum\limits_{T\subseteq S,T\ne\varnothing}p_T=\sum\limits_{T\subseteq S,T\ne\varnothing}\sum\limits_{T\subseteq R\subseteq S}(-1)^{|R|-|T|}q_R\\ &=\sum\limits_{R\subseteq S,R\ne\varnothing}q_R\sum\limits_{T\subseteq R,T\ne\varnothing}(-1)^{|R|-|T|}\\ &=\sum\limits_{R\subseteq S,R\ne\varnothing}q_R\sum\limits_{k=1}^{|R|}(-1)^{|R|-k}\dbinom{|R|}{k}\\ &=\sum\limits_{R\subseteq S,R\ne\varnothing}q_R\sum\limits_{k=1}^{|R|-1}(-1)^{k}\dbinom{|R|}{k}\\ &=\sum\limits_{R\subseteq S,R\ne\varnothing}q_R\left(\sum\limits_{k=1}^{|R|}(-1)^{k}\dbinom{|R|}{k}-(-1)^{|R|}\right)\\ \end{aligned} \]我們知道有這樣一條定理(證明見補充證明):
\[\sum\limits_{k=1}^{|S|}(-1)^{k}\dbinom{|S|}{k}=[S=\varnothing], \]於是我們可以得到 \(f\) 的轉移
\[\begin{aligned} f_S&=\sum\limits_{R\subseteq S,R\ne\varnothing}q_R\left(\sum\limits_{k=1}^{|R|}(-1)^{k}\dbinom{|R|}{k}-(-1)^{|R|}\right)\\ &=\sum\limits_{T\subseteq S,T\ne\varnothing}(-1)^{|T|-1}q_T\\ &=\sum\limits_{T\subseteq S,T\ne\varnothing}(-1)^{|T|-1}2^{ways(T,S-T)}\times f_{S-T}, \end{aligned} \]\(ways(T,S-T)\) 可以通過 \(ways(T-lowbit(T))\) 進行 \(O(1)\) 轉移,然後直接幹就完了。時間複雜度 \(O(3^n)\).
#Code
#define ll long long
const int N = (1 << 18) + 10;
const ll MOD = 1e9 + 7;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, u, v, e[20], re[20]; ll pw[310], f[N], num[N], lb[N], cnt[N];
int main() {
read(n); read(m); pw[0] = 1; f[0] = 1;
for (int i = 1; i <= m; ++ i) pw[i] = (pw[i - 1] << 1) % MOD;
for (int i = 1; i < (1 << n); ++ i) cnt[i] = cnt[i >> 1] + (i & 1);
for (int i = 1; i < (1 << n); ++ i) lb[i] = (i & 1 ? 1 : lb[i >> 1] + 1);
for (int i = 1; i <= m; ++ i) read(u), read(v), e[u] |= (1 << v - 1), re[v] |= (1 << u - 1);
for (int i = 1; i < (1 << n); ++ i) {
for (int t = (i - 1 & i), j = i - t; ; t = (t - 1 & i), j = i - t) {
num[j] = num[j - (j & -j)] - cnt[re[lb[j]] & j] + cnt[e[lb[j]] & i - j];
(f[i] += ((cnt[j] & 1 ? 1 : MOD - 1) * pw[num[j]]) % MOD * f[i - j]) %= MOD;
if (!t) break;
}
}
printf("%d", (pw[m] - f[(1 << n) - 1] + MOD) % MOD);
return 0;
}
#補充證明
#Part. 1
我們要求的結論:
\[\sum\limits_{k=1}^{|S|}(-1)^{k}\dbinom{|S|}{k}=[S=\varnothing], \]證明:考慮這樣一個問題:從 \([n]\) 到 \(\{y_1,y_2,\dots,y_k\}\) 的滿射有多少個?
設 \(S\) 為所有 \([n]\) 到 \(\{y_1,y_2,\dots,y_k\}\) 的對映的集合,則 \(|S|=k^n\),定義性質 \(P_i\) 表示“\(y_i\) 不是對映的像”,\(A_i\) 為滿足性質 \(P_i(1\leq i\leq k)\) 的所有從 \([n]\) 到 \(\{y_1,y_2,\dots,y_k\}\) 的集合,顯然對於任意 \(1\leq i\leq k\) 有
\[|A_i|=(k-1)^n, \]同樣的,對於任意 \(1\leq i_1<i_2<\cdots<i_j\leq k\) 有
\[|A_{i_1}\cap\cdots\cap A_{i_j}|=(k-j)^n, \]於是,根據容斥原理所求滿設的個數為
\[\begin{aligned} &|\overline{A_1}\cap\overline{A_2}\cap\cdots\cap\overline{A_k}|\\ =&|S|-\sum_i|A_i|+\sum_{i<j}|A_i\cap A_j|-\cdots+(-1)^k|A_1\cap\cdots\cap A_k|\\ =&\sum_{j=0}^k(-1)^j\dbinom{k}{j}(k-j)^n=\sum_{j=0}^k(-1)^{k-j}\dbinom{k}{j}j^n, \end{aligned} \]再考慮原題的組合意義,可以知道,若 \(k>n\),則一定不存在滿射,若 \(k=n\),則滿射的個數是 \(n!=k!\),於是有
\[\sum_{j=0}^k(-1)^{k-j}\dbinom{k}{j}j^n=\begin{cases}0,&k>n,\\n!,&k=n.\end{cases} \]上式中 \(n=0\) 時即得所求結論(定義 \(0!=1\))。
證畢.