拓扑AC NOIP模拟赛2

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

正解

发现 \(a\)\(b\) 的子集等价于 \(b-a\)\(b\) 的子集。

所以钦定 \(a_i<a_j\)\(a_i+x\)\(a_j+x\) 的子集等价于 \(a_j-a_i\)\(a_j+x\) 的子集。

所以只需要考虑 \(a_j+x\)\(a_j-a_i\) 超集的次数即可。

由于 \(0\leq x<2^k\),所以 \(a_j+x\) 的后 \(k\) 位取遍 \(0\sim 2^k-1\)

我们并不关心第一位是谁,因为第一位是 \(0\) 和是 \(1\) 时相差 \(2^k\),显然不能都取到。

所以直接求 \(a_j-a_i\)\(<2^k\) 的超集是对的。

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 后跑路,并且枚举每个 \(d=a_i\),拿了 \(35pts\)

T3

打了 \(10pts\) 爆搜。

T4

输出了 \(0.5000000000\),获得 \(10pts\)

正解会补的(大概会?)

posted @ 2024-11-11 10:20  Hugoi  阅读(135)  评论(0)    收藏  举报