2020 区域赛(沈阳) M. United in Stormwind fwt + sosdp

60 篇文章 0 订阅

传送门

文章目录

题意:

n n n个试卷,每个试卷有 m m m个问题,每个问题有两个选项 a , b a,b a,b,定义两个试卷不同当且仅当其选中的问题中有一个问题不同。现在问你对于 m m m个问题的所有子集,有多少个子集问题不同的对数 ≥ k \ge k k个。
1 ≤ n ≤ 2 e 5 , 1 ≤ m ≤ 20 , 1 ≤ k ≤ n ∗ ( n − 1 ) 2 1\le n\le2e5,1\le m\le20,1\le k\le\frac{n*(n-1)}{2} 1n2e5,1m20,1k2n(n1)

思路:

我们将不同选项看成 01 01 01串,两个集合不完全相同的话他们的异或肯定不为 0 0 0,对于我们已经选中的子集 S S S来说,他的答案为 F ( S ) = ∑ i = 1 n ∑ j = i + 1 n [ a n s i ⊕ a n s j > 0 ] F(S)=\sum_{i=1}^n\sum_{j=i+1}^n[ans_i\oplus ans_j>0] F(S)=i=1nj=i+1n[ansiansj>0]
考虑将这个式子转换一下,设 c n t [ i ] cnt[i] cnt[i]代表 i i i这个二进制出现了几次, F ( S ) = 1 2 ∗ ∑ i ⊕ j = S c n t [ i ] ∗ c n t [ j ] F(S)=\frac{1}{2}*\sum_{i\oplus j=S}cnt[i]*cnt[j] F(S)=21ij=Scnt[i]cnt[j],这个显然可以 f w t fwt fwt处理出来。
那么考虑 S S S对于哪些子集有贡献,举个例子,比如子集是 110 110 110,那么他有贡献的子集集合是 100 , 110 , 101 , 010 , 011 , 111 100,110,101,010,011,111 100,110,101,010,011,111,一开始没注意到 101 , 011 101,011 101,011这两个集合,以为直接 s o s d p sosdp sosdp求一遍超集一遍子集就有了,显然是不可以的。
正着来不好弄,我们考虑将其容斥一下。
对于 110 110 110,显然他没有贡献到的集合就是 001 001 001的超集,也就是其补集的超集,所以我们做一个容斥,记 G [ S ] G[S] G[S]表示 S S S集合中对应答案相同的点对数量,这个可以用 s o s d p sosdp sosdp求出来超集,答案即为 ∑ i = 0 ( 1 < < m ) − 1 [ n ∗ ( n − 1 ) 2 − G [ i ] > = k ] \sum_{i=0}^{(1<<m)-1}[\frac{n*(n-1)}{2}-G[i]>=k] i=0(1<<m)1[2n(n1)G[i]>=k]

// Problem: United in Stormwind
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/18713/M
// Memory Limit: 2097152 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
//#pragma GCC optimize(2)
#include<cstdio>
#include<iostream>
#include<string>
#include<cstring>
#include<map>
#include<cmath>
#include<cctype>
#include<vector>
#include<set>
#include<queue>
#include<algorithm>
#include<sstream>
#include<ctime>
#include<cstdlib>
#include<random>
#include<cassert>
#define pb push_back
#define mk make_pair
#define Mid ((tr[u].l+tr[u].r)>>1)
#define Len(u) (tr[u].r-tr[u].l+1)
#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define db puts("---")
using namespace std;

//void rd_cre() { freopen("d://dp//data.txt","w",stdout); srand(time(NULL)); }
void rd_ac() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//AC.txt","w",stdout); }
//void rd_wa() { freopen("d://dp//data.txt","r",stdin); freopen("d://dp//WA.txt","w",stdout); }

typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;

const int N=10000010,mod=1e9+7,INF=0x3f3f3f3f;
const double eps=1e-6;

int n,m;
LL a[N],k;
LL f[N],g[N];
char s[N];

void FWT_xor(LL *a,int opt,int N)
{
    for(int i=1;i<N;i<<=1)
        for(int p=i<<1,j=0;j<N;j+=p)
            for(int k=0;k<i;++k)
            {
                LL X=a[j+k],Y=a[i+j+k];
                a[j+k]=(X+Y);a[i+j+k]=(X-Y);
                if(opt==-1)a[j+k]=1ll*a[j+k]/2,a[i+j+k]=1ll*a[i+j+k]/2;
            }
}

int main()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0);
	
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++) {
		scanf("%s",s);
		int now=0;
		for(int j=0;s[j];j++) {
			if(s[j]=='B') now+=1<<j;
		}
		a[now]++;
	}
	FWT_xor(a,1,1<<m);
	for(int i=0;i<1<<m;i++) a[i]=a[i]*a[i];
	FWT_xor(a,-1,1<<m);
	a[0]-=n;
	for(int i=0;i<1<<m;i++) g[i^((1<<m)-1)]=a[i]/2;
	
	for(int i=0;i<m;i++) {
		for(int j=0;j<1<<m;j++) {
			if(j>>i&1) g[j^(1<<i)]+=g[j];
		}
	}
	
	int ans=0;
	for(int i=1;i<1<<m;i++) {
		LL now=1ll*n*(n-1)-g[i]*2;
		if(now>=k*2) ans++;
	}
	cout<<ans<<endl;
	
	

	return 0;
}
/*

110 -> 001
001 -> 110
111 -> 000


010 
101
011

110 +1 -> 100 010 111 101 011 -> 100 110 101 111 | 010 110 011 111 
001 +1
111 +1

100 +1 +1
010 +1 +1
001 +1 +1
110 +1
101 +1 +1 +1
011 +1 +1 +1
111 +1 +1 +1


*/









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值