【学习笔记】NOI 模拟赛 t3 exam

点这里看题

因为版权问题啥的也不好公开,就将就着看吧。

好题。考场上观察出了一点苗头,可惜没能 A C AC AC

事实上,这道题目并不需要高深的数学知识,只不过需要用 F M T FMT FMT进行反复正变换和逆变换罢了。给出题人点赞。

首先这是一个或运算的卷积。考虑将或运算转化为子集和运算,因为我们对 F M T FMT FMT是熟知的。换句话说,对于 i ∈ [ 0 , 2 m ) i\in [0,2^m) i[0,2m),记 v i = ∏ j ≤ n p j , i v_i=\prod_{j\le n}p_{j,i} vi=jnpj,i { p j } \{p_j\} {pj}显然就是做完子集和后的序列,长度为 2 m 2^m 2m,但是 其本质不同的值只有 2 k 2^k 2k。最后对 v i v_i vi做逆 F M T FMT FMT就是答案,如果对 F M T FMT FMT比较熟知的话应该是可以快速反应过来的。我考场上就没这么快的反应能力。

观察上述问题。点乘是容易的,肯定不会 O ( n 2 m ) O(n2^m) O(n2m)暴力去乘,因为如果是有位运算参与的点乘的话可以先打上标记到最后再来做子集乘。显然我们也不能对于 n n n行都去做长度为 2 m 2^m 2m的卷积,否则点乘也只能暴力了。

考虑替代的思想。先抛出结论,对第 i i i行的所有元素做长度为 2 k 2^k 2k的逆 F M T FMT FMT,然后对于 i ∈ [ 0 , 2 k ) i\in [0,2^k) i[0,2k),设这些数与起来的结果是 s e t i set_{i} seti,做完逆 F M T FMT FMT后结果是 v a l i val_i vali,那么 v s e t i ← v s e t i × v a l i v_{set_i}\gets v_{set_i}\times val_i vsetivseti×vali。只需要最后对长度为 2 m 2^m 2m的数组做一次 F M T FMT FMT即可。

为什么这样是对的?不妨这样来看,对于一个特定的 j ∈ [ 0 , 2 m ) j\in [0,2^m) j[0,2m),设有用的元素的集合为 S j S_j Sj,显然应该点乘上 v a l [ S j ] val[S_j] val[Sj]。显然 S j S_j Sj的子集的 s e t v ( v ⊆ S j ) set_v(v\sube S_j) setv(vSj)也是 s e t S j set_{S_j} setSj的子集,因为已经做了一遍逆 F M T FMT FMT,所以做完前缀积过后就是 v a l [ S j ] val[S_j] val[Sj]。同时,如果 v ⊈ S j v\nsubseteq S_j vSj那么也不会被统计到。于是就做完了。

复杂度 O ( m 2 m + n k 2 k ) O(m2^m+nk2^k) O(m2m+nk2k)。注意做逆 F M T FMT FMT的时候要乘的逆元其实是可以同时处理的,这样会少一个 log ⁡ \log log

自己做的时候yy了一些结论。最后发现还是没有上面写的简洁。看来还是在工具的选择上出了一点问题。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
using namespace std;
const int mod=19260817;
//dj yyds!!!
int n,m,K,a[100005][7],sets[1<<7],ones[1<<7],vones[1<<22];
ll fpow(ll x,ll y=mod-2){
	ll z(1);
	for(;y;y>>=1){
		if(y&1)z=z*x%mod;
		x=x*x%mod;
	}
	return z;
}
ll p[100005][7],pp[1<<7],invpp[1<<7],v[1<<22],inv=fpow(1e6),res;
int lowbit(int x){return x&-x;}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
	cin>>n>>m>>K;
	for(int i=0;i<1<<m;i++)v[i]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<K;j++){
			cin>>p[i][j];
		}
		for(int j=0;j<K;j++){
			cin>>a[i][j];
		}
		for(int j=0;j<1<<K;j++){
			pp[j]=sets[j]=0;
			for(int k=0;k<K;k++){
				if(j>>k&1)pp[j]+=p[i][k],sets[j]|=a[i][k];
			}
			if(pp[j]!=0){
				invpp[j]=fpow(pp[j]),ones[j]=1;
			}
			else{
				pp[j]=invpp[j]=1,ones[j]=0;
			}
		}
		for(int j=0;j<K;j++){
			for(int k=0;k<1<<K;k++){
				if(k>>j&1){
					pp[k]=pp[k]*invpp[k^(1<<j)]%mod,ones[k]-=ones[k^(1<<j)];
					invpp[k]=invpp[k]*pp[k^(1<<j)]%mod;
				}
			}
		}
		for(int j=0;j<1<<K;j++){
			v[sets[j]]=v[sets[j]]*pp[j]%mod;
			vones[sets[j]]+=ones[j];
		}
	}
	for(int i=0;i<m;i++){
		for(int j=0;j<1<<m;j++){
			if(j>>i&1){
				v[j]=v[j]*v[j^(1<<i)]%mod,vones[j]+=vones[j^(1<<i)];
			}
		}
	}
	ll fac3=1,tmp=fpow(inv,n);
	for(int i=0;i<1<<m;i++){
		assert(vones[i]<=n);
		assert(vones[i]>=0);
		if(vones[i]!=n)v[i]=0;
	}
	for(int i=0;i<m;i++){
		for(int j=0;j<1<<m;j++){
			if(j>>i&1){
				v[j]=(v[j]-v[j^(1<<i)])%mod;
			}
		}
	}
	for(int i=0;i<1<<m;i++){
		v[i]=(v[i]*tmp%mod+mod)%mod;
		res^=(fac3*v[i]%mod);
		fac3=fac3*3%mod;
	}
	cout<<res;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值