1. 程式人生 > 其它 >[考試總結]ZROI-21-NOIP衝刺-TEST4 總結

[考試總結]ZROI-21-NOIP衝刺-TEST4 總結

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\) 都順序/逆序排序後一一對應得到的答案最大。

我們可以得到變換後 \(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\)

的直線用奇偶分組字首和處理。時間複雜度 \(O(n)\).

#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) \]

本來寫到這就結束了,但是突然意識到

\[\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, \]

就是環上的答案,不需要額外多算其他的部分。

#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\))。

證畢.