正睿2018省选联测round8t1(dp)

【问题描述】

针⽼师和⼩ C 在玩传统的取⽯⼦游戏,他们有 n 堆⽯⼦,第 i 堆⽯⼦
有 ai 个,两个⼈轮流操作,每次操作需要选择⼀堆⾮空的⽯⼦,然后取⾛
这堆⽯⼦⾥⾄少 1 颗⽯⼦,不能操作的⼈输,⼩ C 先⼿

现在针⽼师得到了⼀次作弊的机会:在游戏开始之前他可以动⽤膜法
删除某 k 堆⽯⼦,要求 k 是 d 的倍数,且 0 ≤ k < n。

针⽼师想知道,他有多少种作弊的⽅案,使得作弊后他⼀定必胜。

你只需要输出⽅案数对 1 0 9 + 7 10^9 + 7 109+7 取模的值。

【输入格式】

第⼀⾏两个正整数 n, d。
第⼆⾏ n 个正整数,第 i 个正整数表⽰ ai

【输出格式】

输出⽅案数对 1 0 9 + 7 10^9 + 7 109+7 取模的值。

【样例输入】

5 2
1 2 3 1 1

【样例输出】

4

【数据范围】

对于前 30% 的数据,有 1 ≤ n ≤ 20 1 ≤ n ≤ 20 1n20
对于前 50% 的数据,有 1 ≤ n ≤ 50 , 1 ≤ a i ≤ 1 0 4 1 ≤ n ≤ 50,1 ≤ a_i ≤ 10^4 1n501ai104
另有 20% 的数据,满⾜不同的 ai 最多只有 5 种。
对于 100% 的数据,有 1 ≤ n ≤ 5 ∗ 1 0 5 , 1 ≤ d ≤ 10 , 1 ≤ a i ≤ 1 0 6 , 1 ≤ ∑ i = 1 n a i ≤ 1 0 7 。 1 ≤ n ≤ 5 ∗ 10^5,1 ≤ d ≤ 10,1 ≤ ai ≤ 10^6, 1 ≤ ∑_{i=1}^n a_i ≤ 10^7。 1n51051d101ai1061i=1nai107


s o l u t i o n solution solution:
朴素 d p dp dp f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑了前 i i i个,选了 x % d = j x\% d=j x%d=j个元素,异或和为 k k k的方案数。
复杂度 O ( n d max ⁡ { a i } ) O(nd \max\{a_i\}) O(ndmax{ai})
考虑优化,注意到一个重要的性质 ∑ i a i ≤ 1 0 7 \sum_ia_i\le 10^7 iai107

我们考虑将 { a i } \{a_i\} {ai}降序排序,考虑对于第 i i i个元素,找到最小的 l l l满足 2 l &gt; a i 2^l&gt;a_i 2l>ai
那么我们知道此时的 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]中,如果 k &gt; = 2 l k&gt;=2^l k>=2l,那么必定是转移不到 0 0 0的,就有一个剪枝了

新复杂度?
g ( l , r ) g(l,r) g(l,r)表示值在 [ l , r ] [l,r] [l,r]中的数的个数
那么复杂度为
O ( d ∗ ∑ i = 1 20 c n t [ 2 i − 1 , 2 i − 1 ] ∗ 2 i ) ≈ O ( d ∑ i = 1 20 a i ) O(d*\sum_{i=1}^{20}cnt[2^{i-1},2^i-1]*2^i)\approx O(d\sum_{i=1}^{20}a_i) O(di=120cnt[2i1,2i1]2i)O(di=120ai)

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x],y = e[i].y;i;i = e[i].n,y = e[i].y)
#define P pair<int,int>
#define Pil pair<int,ll>
#define Pli pair<ll,int>
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
const int p = 1e9+7;
int n,d,to;
int a[501000],bin[30];
int f[2][15][2010000];
inline void add(int &a,int b){a += b;if(a >= p) a -= p;}
int main()
{
	bin[0] = 1;rep(i,1,29) bin[i] = bin[i-1]<<1;
	n = rd();d = rd();
	rep(i,1,n) a[i] = rd(),to^=a[i];
	sort(a+1,a+n+1);reverse(a+1,a+n+1);	
	int mx = 29;
	while(bin[mx-1]>a[1]) mx--;
	f[0][0][to] = 1;
	rep(i,1,n)
	{
		if(bin[mx-1]>a[i]) mx--;
		rep(j,0,d-1) rep(k,0,bin[mx])
		{
			f[i&1][j][k] = 0;
	        add(f[i&1][j][k],f[!(i&1)][j?j-1:j+d-1][k^a[i]]);
	        add(f[i&1][j][k],f[!(i&1)][j][k]);
		}
	}
	printf("%d\n",f[n&1][0][0]-(n%d==0));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值