拓撲AC NOIP模擬賽2

Hugoi發表於2024-11-11

100+35+10+10 拿下 rk7,拓撲AC的A題也太過困難了吧……

T1

題意

給定陣列 \(a\),陣列長度為 \(n\)

定義 \(f(x)\) 表示有多少對 \((i,j)\) 滿足 \((a_i+x)\)\((a_j+x)\) 的子集。

給定 \(k\),保證 \(a_i<2^k\),求 \(\sum_{i=0}^{2^{k-1}}f(i)\)

\(n\leq20000,k\leq 60\)

賽時思路

想對這個玩意進行轉化。

首先轉化成了位運算形式,即 \((a_i+x)|(a_j+x)==a_j+x\)

然後就可以列舉 \(i,j,k\) 了,能拿 \(15pts\)

現在又兩條路走:要麼對於每個 \(f(x)\) 單獨考慮,要麼考慮每對 \((i,j)\) 對答案的貢獻。

一般這種題第二條路都更像是正解。

然後要進行進一步轉化,我就不會了。

急急急!打完 \(O(n^2 2^k)\) 後罰坐至比賽開始兩個多小時。

看看每兩個數之間的貢獻是否有什麼規律吧。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000;
int n,k,num[N][N],a[N],ans;
signed main(){
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
	freopen("out.out","w",stdout);
#endif
	ios::sync_with_stdio(0);
	k=3;
	n=1<<k;
	for(int i=1;i<=n;i++){
		a[i]=i-1;
	}
	for(int i=0;i<(1<<k);i++){
		for(int j=1;j<=n;j++){
			for(int l=1;l<=n;l++){
				if(((a[j]+i)|(a[l]+i))==a[l]+i){
					num[j][l]++;
					ans++;
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cout<<num[i][j]<<' ';
		}
		cout<<'\n';
	}
	cout<<ans<<'\n';
}

先打出來 \(k=3\) 時每個數對的貢獻看看。

輸出:

8 4 4 2 4 2 2 1 
0 8 4 4 2 4 2 2 
0 0 8 4 4 2 4 2 
0 0 0 8 4 4 2 4 
0 0 0 0 8 4 4 2 
0 0 0 0 0 8 4 4 
0 0 0 0 0 0 8 4 
0 0 0 0 0 0 0 8 

嗯?怎麼都是 \(2\) 的冪次?

再大一點呢?

16 8 8 4 8 4 4 2 8 4 4 2 4 2 2 1 
0 16 8 8 4 8 4 4 2 8 4 4 2 4 2 2 
0 0 16 8 8 4 8 4 4 2 8 4 4 2 4 2 
0 0 0 16 8 8 4 8 4 4 2 8 4 4 2 4 
0 0 0 0 16 8 8 4 8 4 4 2 8 4 4 2 
0 0 0 0 0 16 8 8 4 8 4 4 2 8 4 4 
0 0 0 0 0 0 16 8 8 4 8 4 4 2 8 4 
0 0 0 0 0 0 0 16 8 8 4 8 4 4 2 8 
0 0 0 0 0 0 0 0 16 8 8 4 8 4 4 2 
0 0 0 0 0 0 0 0 0 16 8 8 4 8 4 4 
0 0 0 0 0 0 0 0 0 0 16 8 8 4 8 4 
0 0 0 0 0 0 0 0 0 0 0 16 8 8 4 8 
0 0 0 0 0 0 0 0 0 0 0 0 16 8 8 4 
0 0 0 0 0 0 0 0 0 0 0 0 0 16 8 8 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 8 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 16 

還是對的!並且對角線上數一樣!

這就意味著我們只需要得到第一行就可以算任意位置的貢獻了!

對第一行取個 \(\log\) 看看呢?

k=4: 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0 
k=5: 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0 
k=6: 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0 
k=7: 7 6 6 5 6 5 5 4 6 5 5 4 5 4 4 3 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 6 5 5 4 5 4 4 3 5 4 4 3 4 3 3 2 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 5 4 4 3 4 3 3 2 4 3 3 2 3 2 2 1 4 3 3 2 3 2 2 1 3 2 2 1 2 1 1 0 

好像非常有規律啊。

首先 \(k-1\) 的表是 \(k\) 的表的字尾。

然後從後往前4位4位地考慮,發現每4位都是形如:\(a+2,a+1,a+1,a\) 的。

\(2 1 1 0\) 開始,每次變換就是把每個數當成 \(a\) 後變成四個數。

答案就是變換的字尾。

但是這樣都能考慮了,為什麼不2位2位考慮呢?

然後再看了一會,發現這玩意其實就是相應下標的 \(popcount\)

\(popcount(0)=0\)

\(popcount(1)=1\)

\(popcount(2)=1\)

\(popcount(3)=2\)

……

然後就會了,\(O(n^2)\) 列舉後把規律糊上去就行了。

後來又被 1ll<<k 和 __builtin_popcountll 硬控了半個小時。

#include<bits/stdc++.h>
#define W "w"
#define int long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,k,a[N],ans,pw[100];
signed main(){
	freopen("subset.in","r",stdin);
	freopen("subset.out",W,stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>k;
	pw[0]=1;
	for(int i=1;i<=k;i++) pw[i]=pw[i-1]*2%mod;
	for(int i=1;i<=n;i++) cin>>a[i];
	int all=(1ll<<k);
	for(int i=1;i<=n;i++) a[i]++;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i]>a[j]) continue;
			int now=all-a[j]+a[i]-1;
			(ans+=pw[__builtin_popcountll(now)])%=mod;
		}
	}
	cout<<ans<<'\n';
}

T2

題意

給定陣列 \(a\),長度為 \(n\)

\(a_i\) 變成 \(x\) 需要付出 \(|a_i-x|\) 的代價。

求將 \(a\) 中每個數都變成 \(d\) 的冪次的最小代價。

\(d\) 可任選。

\(V=max(a_i)\)

\(n\leq 100000,1\leq V\leq 10^{12}\)

賽時思路

如果 \(d\) 固定的話,我會 \(O(n)\)

所以問題在於確定 \(d\)

二分?這玩意好像沒啥單調性。

根號分治!

對於 \(d\leq\sqrt V\) ,可以列舉 \(d\),能做到 \(O(n\sqrt V)\)

(實際上可以更優,但是我場上沒想到,所以一直覺得根號分治不是正解。

對於 \(d>\sqrt V\),我不會!

於是將 \(d\) 列舉到 600 後跑路,拿了 \(35pts\)

T3

打了 \(10pts\) 爆搜。

T4

輸出了 \(0.5000000000\),獲得 \(10pts\)

正解會補的(大概會?)