「0825提高組模擬賽」
「0825提高組模擬賽」
-
「T1」
-
考場上沒什麼思路。。。
-
首先,不需要知道每一次操作的 \(b\) 具體是什麼數,只要關心相對大小就行
-
所以 \(b\) 可以看錯一個長度為 \(m\) 的排列,最終的答案 \(\times\dbinom{S}{m}\) 即可 (\(S\) 很大,用 \(\frac{S(S-1)..(S-m+1)}{m(m-1)...1}\) )
-
考慮如果將 \([1,n]\) 按照影響的關係建圖,即 \(x\) 連向 \(x-lowbit(x)\) (其中 \(x\) 為兒子),那麼一個節點子樹內的節點數即能影響該節點的數的個數,有樹狀陣列的基本操作可以知道,包括這個節點在內一共有 \(lowbit(x)\)
-
將樹狀陣列的每一位分開來考慮
-
假設當前考慮到第 \(i\) 位,記 \(len=lowbit(i)\)
-
那麼更改這個節點的方案數為 \(\dbinom{m}{x}len^x\times(n-len)^{m-x}\) ,\(x\) 為選出的運算元
-
怎麼計算更改的貢獻?
-
可以發現每組操作 \(d[i]\) 不減,所以需要確定 \(x\) 次操作 \(d[i]\) 被分成了幾段不同的數
-
考慮一個排列的期望貢獻是多少?
-
第 \(i\) 次操作為最大的概率為 \(\frac{1}{i}\) ,由於價值為 \(1\),所以期望也為 \(i\)
-
總共有 \(m!\)
-
所以最終的答案為 \(\dbinom{m}{x}len^x\times(n-len)^{m-x}m!\sum_{i=1}^{x}\frac{1}{x}\)
-
優化:
-
預處理出 \(x!\) ,\(len^x\) 隨著 \(x\) 的遞增累乘
-
注意到 \(n\) 很大,對於同樣的 \(lowbit\) 一起處理,當 \(lowbit()\) 為 \(i\) 的一共有 \((n/i-n/(i<<1))\) 個數( 後 \(log_2i\) 為 \(0\) - 後 \((log_2i)+1\) 位為 \(0\) )
-
「T2」
-
考場上在推容斥,基本耗在這道題上了
-
對於行列以 \(\sqrt n\) 為界分開計算
-
「T3」
-
考場上直接判斷 \(n-2\) ,\(n-1\) 兩種情況,想當然了,最後拍出來,心態不穩,最後一分鐘加了一個 \(break\) ,少了 \(30\) 分(最後 \(10\) 分鐘拒絕對拍,最後 \(5\) 分鐘拒絕改程式碼細節)
-
依照 \(NOI2020Day2T1\) 的結論,可以得到答案一定 \(<=n-1\) 且在 \(n-1\) 時一定有解
-
當 \(ans<n-1\) 時,將現在的物品分成 \(n-ans\) 塊,假設第 \(i\) 塊所選的集合為 \(S\),則需滿足 \((|S|-1)*\frac{\sum V_i}{n}=\sum_{i屬於S}V_i\)
-
列舉答案 \(x\) ,記錄 \(sum[i]\) 代表選擇點的狀態為 \(i\) 的時候能分成的最大塊數,\(O(3^n*n)\) 狀壓
-
優化:
-
依然列舉答案 \(x\) ,再列舉 \(i\) 代表所分成的塊數,\(sum[s]\) 表示當前情況下選擇 \(s\) 集合是否可能
-
如果 \(s\) 能被分成 \(i\) 塊且 \(s'\) 能被分成 \(i-1\) 塊(\(s'\) 為 \(s\) 的子集),則轉移,另開一個數組記錄 \(s\) 的子集能否被分成 \(i-1\) 塊,複雜度可以做到 \(O(2^n*n^3)\)
-
將列舉答案的過程狀壓進 \(sum\) 裡,可以做到 \(O(2^n*n^2)\)
#include<bits/stdc++.h> using namespace std; const int Max_N=20; int n,V[Max_N+5],g[1<<Max_N],b[1<<Max_N],h[1<<Max_N],sum[1<<Max_N]; long long f[1<<Max_N]; int main(){ freopen("juice.in","r",stdin); freopen("juice.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&V[i]); for(int i=1;i<=n;i++) b[1<<i-1]=i; for(int i=1;i<1<<n;i++) f[i]=f[i-(i&-i)]+V[b[i&-i]],g[i]=g[i-(i&-i)]+1; int ans=max(n-1,1); sum[0]=(1<<n)-1; for(int i=1;i<=n-(n+1)/2;i++){ for(int j=0;j<1<<n;j++){ h[j]=sum[j]; for(int k=1;k<=n;k++) if((1<<k-1)&j) h[j]|=h[j-(1<<k-1)]; sum[j]=0; }//h[i]記錄i的子集是否存在一種方案被分成i-1塊 for(int j=0;j<1<<n;j++) for(int k=(n+1)/2;k<=n-2;k++) if(f[(1<<n)-1]*(g[j]-i)==f[j]*k)//判斷是否能分成i塊 sum[j]|=h[j]&1<<k; if((1<<(n-i))&sum[(1<<n)-1]) ans=n-i; } printf("%d\n",ans); return 0; }