BZOJ4851: [Jsoi2016]位运算

BZOJ

题意

你需要在 [ 0 , G − 1 ] [0,G-1] [0,G1]中选出 n n n个不同的数,使它们异或起来等于 0 0 0,问有多少种不同的方案数; G G G是由一个二进制位小于等于 50 50 50的二进制数 S S S重复 K K K次得到的.

题解

我们考虑选出的 n n n个数 A i A_i Ai按从大到小排序,那么每次我们取出来的数是这样的:
G > A n > A n − 1 > A n − 2 . . . > A 2 > A 1 G>A_n>A_{n-1}>A_{n-2}...>A_2>A_1 G>An>An1>An2...>A2>A1;
这样就能保证我们取的方案是不同的;
接下来考虑n个二进制长度为 ∣ S ∣ |S| S的数该怎么取;
我们用一个 01 01 01序列来表示当前 n n n个数的大小关系比如 10011 10011 10011表示 G > A 5 ≤ A 4 ≤ A 3 > A 2 > A 1 G>A_5 \leq A_4 \leq A_3>A_2>A_1 G>A5A4A3>A2>A1;
现在我们考虑一位一位的得到这 n n n个数的大小关系,已知当前位的大小关系时,只需要再枚举下一位 n n n个数的二进制位,就能推出下一位的大小关系;
F i j F_{ij} Fij表示当前大小关系状态 i i i ∣ S ∣ |S| S位后到状态 j j j一共有多少种方式,于是我们便能用如上的状压递推的方式得到这个 F F F;
接下来的考虑取了 i i i ∣ S ∣ |S| S位大小关系是 j j j时一共有多少种方案,因为每 ∣ S ∣ |S| S个之间的转移是一样的,取 i i i ∣ S ∣ |S| S那么长实际上就是取 F F F i i i次方,能够使用矩阵快速幂快速得到;
复杂度 O ( 2 n ∗ 3 ∗ ∣ S ∣ + 2 n ∗ 3 ∗ l o g k ) O(2^{n*3}*|S|+2^{n*3}*logk) O(2n3S+2n3logk);

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
	char c = getchar();
	bool f = false;
	for (x = 0; !isdigit(c); c = getchar()) {
		if (c == '-') {
			f = true;
		}
	}
	for (; isdigit(c); c = getchar()) {
		x = x * 10 + c - '0';
	}
	if (f) {
		x = -x;
	}
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
	read(x), read(y...);
}
const int P=1e9+7;
int n,K,len,LIM;
int tmp[55],Next[1<<7][1<<7][55],F[55][1<<7];
char S[55];
struct Matrix {
	int M[1<<7][1<<7];
}Bas;
typedef Matrix Mt;
Mt operator *(Mt A,Mt B) {
	Mt C;
	for(int i=0;i<LIM;++i)
		for(int j=0;j<LIM;++j) {
			C.M[i][j]=0;
			for(int k=0;k<LIM;++k)
				C.M[i][j]=(C.M[i][j]+1ll*A.M[i][k]*B.M[k][j]%P)%P;
		}
	return C;
}
Mt Pow(Mt A,int k) {
	Mt res;
	for(int i=0;i<LIM;++i)
		for(int j=0;j<LIM;++j)
			res.M[i][j]=(i==j);
	while(k) {
		if(k&1) res=res*A;
		A=A*A;
		k>>=1;
	}
	return res;
}
int cmp(int a,int b,int c) {
	if(c) return 1;
	if(a>b) return -1;
	return a^b;
}
int main() {
	read(n,K); scanf("%s",S+1); len=strlen(S+1);
	LIM=1<<n;
	for(int i=0;i<LIM;++i) {
		for(int j=0;j<LIM;++j) {
			int cnt=0;
			for(int k=0;k<n;++k) tmp[k+1]=j>>k&1,cnt+=tmp[k+1];
			if((cnt&1)^1) {
				for(int k=1;k<=len;++k) {
					int S1=0; bool OK=true; tmp[0]=S[k]-'0';
					for(int l=1;l<=n;++l) {
						int t=cmp(tmp[l],tmp[l-1],i>>(l-1)&1);
						if(~t) S1|=t<<(l-1);
						else {
							OK=false;
							break;
						}
					}
					if(OK) Next[i][j][k]=S1;
					else Next[i][j][k]=-1;
				}
			}
			else for(int k=1;k<=len;++k) Next[i][j][k]=-1;
		}
	}
	for(int i=0;i<LIM;++i) {
		mem(F,0);
		F[0][i]=1;
		for(int j=1;j<=len;++j) {
			for(int k=0;k<LIM;++k) if(F[j-1][k]) {
				for(int l=0;l<LIM;++l) {
					int v=Next[k][l][j];
					if(~v) F[j][v]=(F[j][v]+F[j-1][k])%P;
				}
			}
		}
		for(int j=0;j<LIM;++j) Bas.M[i][j]=F[len][j];
	}
	Bas=Pow(Bas,K);
	printf("%d\n",Bas.M[0][LIM-1]);		
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值