2019牛客国庆集训派对day5 K 2017 Revenge 【原根】+【bitset优化dp】

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
Special Judge, 64bit IO Format: %lld

题目描述

Bobo has n integers a1,a2,…,ana_1, a_2, \dots, a_na1​,a2​,…,an​.
He would like to choose some of the integers and calculate their product (the product of the empty set is defined as 1).

Bobo would like to know the number of products whose remainder divided by 2017 is r. As the exact number is too large, he only asks for the number modulo 2.

输入描述:

The input contains zero or more test cases and is terminated by end-of-file.  For each case,
The first line contains two integers n, r.
The second line contains n integers a1,a2,…,ana_1, a_2, \dots, a_na1​,a2​,…,an​.

* 1≤n≤2×1061 \leq n \leq 2\times 10^61≤n≤2×106
* 1≤r,a1,a2,…,an<20171 \leq r, a_1, a_2, \dots, a_n < 20171≤r,a1​,a2​,…,an​<2017
* The sum of n does not exceed 2×1062 \times 10^62×106.

输出描述:

For each case, output an integer which denotes the parity.

示例1

输入

3 6
2 3 4
4 1
1 1 2016 2016

输出

1
0

本题需要用到数论中原根的知识再结合bitset来优化dp。

首先由题意可以想到一个裸的dp, dp(i,j)表示前i个数,选出来的乘积模2017为j的方案数,转移方程为dp(i,j)=(dp(i-1,\frac{j}{a[i]}\ mod\ 2017)+dp(i-1,j))\ mod\ 2,初始状态为dp[0][1]=1。然而总状态数为大约2e9,显然会T。

因为只需要求方案数模2,或许我们可以联想到位运算异或,如果能将一层转移(i相同的转移)转化为常数次bitset的操作,计算规模就能降到 \frac{2*10^9}{64}=3.125*10^7,即可通过本题。

要想只通过常数次bitset操作转移,那么我们希望转移是连续的,比如从[l,r]转移到[x,y],其中r-l+1==y-x+1,即区间长度相同,且大小关系也一一对应,对于这样的区间只需要一次bitset操作即可完成转移。

2017是一个奇素数,原根必存在。对于一个奇素数p,模p的原根可以在指数取[1,p-1]区间时,模p得到[1,p-1]的所有值。

模2017有原根5,那么我们可以用5^{I[x]}\ mod\ 2017来表示模2017为x的数,那么乘法j*x\ mod\ 2017就可以转化为指数上的加法5^{I[j]+I[x]}\ mod\ 2017,则我们可以通过映射,使得转移是连续的。

令dp(i,j)表示前i个数,选出来的乘积为5^{j}\ mod\ 2017的方案数,转移方程为

dp(i,j)=dp(i-1,j)\ xor\ dp(i-1,j-I[a[i]])\ if \ j\in [I[a[i]],2015]\\ dp(i,j)=dp(i-1,j)\ xor\ dp(i-1,j+2016-I[a[i]])\ if\ j \in [0,I[a[i]]-1]

通过滚动数组,只需要一个bitset即可完成转移,有dp=dp^(dp<<I[a[i]])^(dp>>(2016-I[a[i]]))

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e6+5;
int n,r,I[2017],a[maxn];//5^I[i]%2017 equal i, 5 is 2017's 原根
bitset<2016> dp;//5^[0,2015]%2017 equal [1,2016]%2017
int main(){
	//freopen("in.txt","r",stdin);
	int tt=1;
	for(int i=0;i<2016;i++){
        I[tt]=i;
        tt=tt*5%2017;
	}
	while(~scanf("%d %d",&n,&r)){
		for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
		}
		dp.reset();
		dp.set(0);//no number was selected
		for(int i=1;i<=n;i++){//转移有两种:dp[i][j]=dp[i-1][j]+dp[i-1][j-I[a[i]]],后者又可拆为[0,2015-I[a[i]]]转移到[I[a[i]],2015],[2016-I[a[i]],2015]转移到[0,I[a[i]]]
            dp^=(dp<<I[a[i]])^(dp>>(2016-I[a[i]]));
		}
		cout<<dp[I[r]]<<"\n";
	}
	return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值