1. 程式人生 > 實用技巧 >「0825提高組模擬賽」

「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;
    }