BZOJ4762 最小集合

84 篇文章 1 订阅
8 篇文章 0 订阅
该博客主要探讨了如何解决BZOJ4762问题,通过将所有数取反,转化为求使得所有数的或和为全集的方案,并确保任意子集的或和不等于全集。博主使用动态规划方法,定义状态f[i][j][k]表示在前i个数中选择,和为j,且k为后n-i个数中选出部分的子集。博客中详细阐述了状态转移方程,最终将时间复杂度优化到n*3^m。
摘要由CSDN通过智能技术生成

先考虑把所有数取反,问题变成求使得所有数的或和为全集,且任意一个缺一个数的子集的或和都不为全集的方案数

考虑dp,f[i][j][k]表示在前i个数中选了一些,这些数或和为j,要求k是后n-i个数里选出来的数的或和的子集的方案数

不要问我为什么会有这种鬼畜状态-_-

那么考虑转移,设第i+1个数为x,假设第i+1个数不选,那么f[i+1][j][k]+=f[i][j][k]

如果第i个数选,那么我们先保证x不是前面选的数的子集,即以下转移要求x&j!=x

经WerKeyTom_FTD神犇提醒,观察下面的转移式,我们发现当x&j==x的时候,相当于没做转移,所以不用判这个

那么考虑用所有的方案减去x是后面选的数的子集的方案,即

f[i+1][j|x][k^(k&x)]+=f[i][j][k]//所有的的方案

f[i+1][j|x][(k^(k&x))|(x^(x&j))]-=f[i][j][k]//不合法的方案

那么这样复杂度就是n*4^m的,m为二进制位的个数

观察转移式中k那一维的表达式,初始值为f[0][0][0]=1,那么瞎jb归纳以下可得f[i][j][k]!=0的必要条件是k是j的子集

那么复杂度就降到n*3^m了

#include<iostream>
#include<cstring>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<cstdlib>
#include<cstdio>
#include<map>
#include<bitset>
#include<set>
#include<stack>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 1024
#define MAXM 1010
#define ll long long
#define eps 1e-8
#define MOD 1000000007
#define INF 1000000000
int N=1023;
int n;
int f[MAXN][MAXN],g[MAXN][MAXN];
int main(){
	int i,j,k,x;
	scanf("%d",&n);
	f[0][0]=1;
	for(i=1;i<=n;i++){
		scanf("%d",&x);
		x^=N;
		for(j=0;j<=N;j++){
			for(k=j;;k=k-1&j){
				g[j][k]=0;
				if(!k){
					break;
				}
			}
		}
		for(j=0;j<=N;j++){
			if((x&j)!=x){	
				for(k=j;;k=k-1&j){
					(g[j|x][k&(~x)]+=f[j][k])%=MOD;
					(g[j|x][(k&(~x))|(x&(~j))]+=MOD-f[j][k])%=MOD;
					if(!k){
						break;
					}
				}
			}
		}
		for(j=0;j<=N;j++){
			for(k=j;;k=k-1&j){
				(f[j][k]+=g[j][k])%=MOD;
				if(!k){
					break;
				}
			}
		}
	}
	printf("%d\n",f[N][0]);
	return 0;
}

/*
4
1 2 4 4

*/


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值