一道容斥练习题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
首先这个题可以记忆化搜索,不过直接记忆化复杂度是不能满分的,需要加可行性剪枝:就是当前的状态肯定无解时,就要直接返回。

#include<bits/stdc++.h>
using namespace std;
const int maxn=55,lim=(1<<21)-1;
inline int read(){
    char c=getchar();int t=0,f=1;
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
    return t*f;
}
int n,a[maxn],cho[maxn],ans,suf[maxn];
struct pii{
    int a,b;
    pii(int aa=0,int bb=0){a=aa,b=bb;}
};
bool operator <(pii a,pii b){
    if(a.a==b.a)
    return a.b<b.b;
    return a.a<b.a;
}
map<pii,long long> mp[55];
int alfa1,alfa2;
long long dfs(int now,int tmp1,int tmp2){
    alfa1=(tmp1&suf[now])^tmp2,alfa2=(tmp2&suf[now])^tmp1;
    if((alfa1&tmp2)!=alfa1){return 0;}
    if((alfa2&tmp1)!=alfa2){return 0;}
    pii tmp=pii(tmp1,tmp2);
    if(mp[now][tmp]){
        return mp[now][tmp];
    }
    if(now==n+1){
        if(tmp1==tmp2){
            mp[now][tmp]=1;return 1;
        }
        return 0;
    }
    long long ans=0;
    ans+=dfs(now+1,tmp1&a[now],tmp2);
    ans+=dfs(now+1,tmp1,tmp2&a[now]);
    mp[now][tmp]=ans;
    return ans;
}
int main(){
    //freopen("b.in","r",stdin);
    //freopen("b.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    suf[n+1]=lim;
    for(int i=n;i>=1;i--)suf[i]=suf[i+1]&a[i];
    printf("%lld\n",dfs(1,lim,lim));
    return 0;
}

然后正解是按位容斥,具体就是观察这些数的二进制,然后考虑不合法的情况是什么:对于某一位二进制,如果所有在这一位是1的数都被选进了一个集合,那这个情况就不合法,直接算不好算,所以需要 O ( 2 20 ) 枚 举 钦 定 哪 些 位 不 合 法 的 状 态 O(2^{20})枚举钦定哪些位不合法的状态 O(220)容斥,系数就是 − 1 不 合 法 的 位 数 -1^{不合法的位数} 1,然后此时有一个问题,就是不能有一个集合是全空的,所以一开始所有不满足条件的方案数不是 2 n 2^n 2n.
容斥过程中的方案数也没有那么简单。具体看代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,a[55];
bitset<55> alfa[25],beta[25],A,B,C,W;
int len,pos[25],ans;
void dfs(int now,int w,bitset<55> a,bitset<55> b,bitset<55> c){//a维护的是某些位置的值具有相关性(即哪些位置是必须放在一个集合的)
	W=b&c;
	if(W.count())return ;//某个关键集合既出现在a又出现在b,枚举的情况不合法 
	if(now>len){
		if(c.count()&&b.count())ans=ans+w*(1ll<<a.count());
		else ans=ans+w*((1ll<<a.count())-1);//两个集合中的一个没有限制,会出现某个集合是空集的情况,要去掉 
		return ;
	}
	dfs(now+1,w*(-1),a&alfa[pos[now]],b|beta[pos[now]],c);//将所有0加入b集合 
	dfs(now+1,w*(-1),a&alfa[pos[now]],b,c|beta[pos[now]]);//将所有0加入c集合 
	dfs(now+1,w,a,b,c);
}
signed main(){
	//freopen("b.in","r",stdin);
	//freopen("b.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		for(int j=0;j<20;j++){
			alfa[j][i]=(a[i]&(1<<j));
			beta[j][i]=1-alfa[j][i];
		}
	}
	len=0;
	for(int i=0;i<20;i++){
		if((alfa[i].count()!=n)&&(alfa[i].count())){pos[++len]=i;}
	}
	for(int i=1;i<=n;i++)A[i]=1;
	dfs(1,1,A,B,C);
	printf("%lld\n",ans-1);//两个集合都没有限制的情况在上面的dfs中没有考虑 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值