「CTSC2017」吉夫特(Gift) (Lucas定理,状压DP)

题面

链接

简单的题目,既是礼物,也是毒药。

B 君设计了一道简单的题目,准备作为 gift 送给大家。

输入一个长度为 n n n 的数列 a 1 , a 2 , ⋯   , a n a_1, a_2, \cdots , a_n a1,a2,,an 问有多少个长度大于等于 2 2 2 的不上升的子序列满足:
∏ i = 2 k ( a b i − 1 a b i ) m o d    2 = ( a b 1 a b 2 ) × ( a b 2 a b 3 ) × ⋯ ( a b k − 1 a b k ) m o d    2 > 0 \prod_{i=2}^{k}\begin{pmatrix}a_{b_{i-1}}\\a_{b_i}\end{pmatrix}\mod 2=\begin{pmatrix}a_{b_{1}}\\a_{b_2}\end{pmatrix}×\begin{pmatrix}a_{b_{2}}\\a_{b_3}\end{pmatrix}×\cdots\begin{pmatrix}a_{b_{k-1}}\\a_{b_k}\end{pmatrix}\mod 2>0 i=2k(abi1abi)mod2=(ab1ab2)×(ab2ab3)×(abk1abk)mod2>0
输出这个个数对 1000000007 1000000007 1000000007 取模的结果。

……

G 君听说这个题是作为 gift 送给大家,她有一句忠告。

“Vorsicht, Poison!”

“小心. . . . . .礼物! ”

输入格式

第一行一个整数 n n n

接下来 n n n 行,每行一个整数,这 n n n 行中的第 i i i 行,表示 a i a_i ai

输出格式

一行一个整数表示答案。

数据范围

1 ≤ n ≤ 211985 1\leq n\leq 211985 1n211985 1 ≤ a i ≤ 233333 1\leq a_i\leq 233333 1ai233333 (不超过 2 18 2^{18} 218)。
所有的 a i a_i ai 互不相同,也就是说不存在 i , j i, j i,j 同时满足 1 ≤ i < j ≤ n 1\leq i < j\leq n 1i<jn a i = a j a_i = a_j ai=aj

题解

相信大家和我一样,看到这道题就从  ⁣ ⁣ ⁣ m o d    2 > 0 \!\!\!\mod 2>0 mod2>0 开始推起,转换成结果为奇数,然后中间每个组合数都得是奇数,然后再把组合数拆开看怎么达到奇数……

其实转化为每个 C ( a b i − 1 , a b i ) m o d    2 > 0 C(a_{b_{i-1}},a_{b_i})\mod 2>0 C(abi1,abi)mod2>0 就足够了,本来我们看到模数这么小应该先想到Lucas定理的。

用Lucas定理化简一下 C ( A , B ) m o d    2 C(A,B)\mod 2 C(A,B)mod2 ,得到
C ( A & 2 0 , B & 2 0 ) ∗ C ( A & 2 1 , B & 2 1 ) ∗ C ( A & 2 2 , B & 2 2 ) ∗ ⋯ m o d    2 C(A\&2^0,B\&2^0)*C(A\&2^1,B\&2^1)* C(A\&2^2,B\&2^2)*\cdots\mod 2 C(A&20,B&20)C(A&21,B&21)C(A&22,B&22)mod2因此,上式中每一个组合数都得是 1 1 1 ,结果才能是 1 1 1

根据上式,只会出现 C ( 1 , 1 ) , C ( 1 , 0 ) , C ( 0 , 0 ) , C ( 0 , 1 ) C(1,1),C(1,0),C(0,0),C(0,1) C(1,1),C(1,0),C(0,0),C(0,1) 这四种组合数,而只有 C ( 0 , 1 ) C(0,1) C(0,1) 不是 1 1 1 而是 0 0 0 ,所以不能出现 C ( 0 , 1 ) C(0,1) C(0,1) ,这就要求 B    &    A = B      ( B ⊆ A ) B\;\&\;A=B\;\;(B\subseteq A) B&A=B(BA)

那么题意成功转化:找出长度大于等于 2 2 2下降子序列 a 1 , a 2 , ⋯   , a n a_1, a_2, \cdots , a_n a1,a2,,an 满足二进制下后一个数是前一个数的子集。

这个其实用一个状压DP就很好做了,每一个数不同,因此可以每一种数字对应一个位置,转移时保证更大的数字位置更靠前(谨记),至于长度大于等于 2 2 2,倒没必要在边界上费心,只需要最后减去长度小于 2 2 2 的就行。

CODE

(认真整了点没用的优化)

#include<ctime>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
#define MAXN 211990
#define MAXM (1<<18|5)
#define DB double
#define ENDL putchar('\n')
#define INF ((LL)1e18)
#define lowbit(x) (-(x) & (x))
LL read() {
    
    
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {
    
    if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {
    
    x=x*10+(s-'0');s = getchar();}
	return f * x;
}
const int MOD = 1000000007;
int n,m,s,i,j,o,k;
int a[MAXN];
int pt[MAXM],ct[MAXM];
int dp1[MAXM],dp2[MAXM],dp[MAXM];
int main() {
    
    
	n = read();m = 1;int CT = 0;
	for(int i = 1;i <= n;i ++) {
    
    
		a[i] = read();
		pt[a[i]] = i;
		while(m-1 <= a[i]) m <<= 1,CT ++;
	}
//	printf("m:%d\n",m);
	pt[0] = n+1;
	for(int i = 1;i < m;i ++) ct[i] = ct[i-lowbit(i)] + 1;
	dp1[m-1] = 1;
	for(int i = m-2;i > 0;i --) {
    
    
		if(ct[i]*2 >= CT && pt[i]) {
    
    
			dp1[i] = 0;
			int ni = (m-1) ^ i;
			for(int j = ni;j > 0;j = (j-1)&ni) {
    
    
				if(pt[i^j] < pt[i]) (dp1[i] += dp1[i^j]) %= MOD;
			}
//			printf("dp1[%d] = %d\n",i,dp1[i]);
		}
	}
	dp2[0] = 1;
	for(int i = 1;i < m-1;i ++) {
    
    
		if(ct[i]*2 < CT && pt[i]) {
    
    
			dp2[i] = 0;
			for(int j = i;j > 0;j = (j-1)&i) {
    
    
				if(pt[i^j] > pt[i]) (dp2[i] += dp2[i^j]) %= MOD;
			}
//			printf("dp2[%d] = %d\n",i,dp2[i]);
		}
	}
	int ans = 0;
	for(int i = 1;i < m;i ++) {
    
    
		if(i == m-1 || (ct[i]*2 >= CT && pt[i])) {
    
    
			for(int j = i;j > 0;j = (j-1)&i) {
    
    
				if((ct[i^j]<<1) < CT && pt[i^j] > pt[i])
					(dp[i] += dp1[i] *1ll* dp2[i^j] % MOD) %= MOD;
			}
		}
		(ans += dp[i]) %= MOD;
	}
	(ans += MOD-(n+1)) %= MOD;
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43960414/article/details/113818872