这个是转载的w
戳这:原文w链接w我吹爆!
我们神奇的jxfgg给拉了这套gym...上来就是高精度简直自闭(队友用java过了感染呜呜呜)
D题 手推 手打了几发一直wa而找到题解才发现自己大方向根本就不对
题目链接:http://codeforces.com/gym/101848/problem/D
题目倒是很短,题意也比较明了w
给你n,p,k三个数,问你在集合{0~(2^n )-1}中有多少个子集满足子集内元素的异或和模p等于k
带下划线来自转载\(^o^)/
思路:
本题用到了费马小定理的知识,以及一个常见的思考 异或和 问题的方式。
我们先思考 (2^n)-1 这个数,不难看出这个数一共有 n 位,我们从这个集合中取出具有下列性质的数,即1、10、100、1000、...、100000这些数,不难看出这些数可以组合出任意的k,除了0。(一开始真的没有理解)
因此对于这个集合来说,将集合中所有1、10、100...这类数都取出来,那么剩下的集合中所有的子集一共是 2^(2^n-n) - 1 这么多种情况,对于每个子集而言都可以在1、10、100的帮助下凑出k。(本来是2^n 个,如果取出n个 就是 2^n - n 个元素,然后因为一个集合的子集有2^n个,非空子集有2^n - 1个,所以现在出去 1,10,100..的非空子集数目是 2^(2^n-n)-1个)
又因为只要 k != 0,则只用1、10、100一定可以凑出 k 这个数,因此我们只需要对 k == 0这种情况特判一下就行。因此问题的关键就是如何求
2^(2^n-n) % p
这里就需要引入费马小定理的知识了。
【注意:该定理成立条件为a、p互素】(百度说的是a只要不是p倍数就ok?)即a^(p-1) %p=1
因此我们只要将 (2^n-n) = x(p-1)+y ,求出 y ,再将 y 进行快速幂运算即可求出答案。2^(2^n-n )-1=1^x*2^y-1;
此处需要注意如果 p == 2,那么a、p 不互素,因此需要进行特判。
还有一处需要注意,求 y 的时候,2^n快速幂是%(p-1),这里不要出错。
除此之外还有一处,因为 k == 0 时,最后的答案需要 -1,不能用负数%p,因此我们需要变成 (ans-1+p) % p,这样就可以求出答案了。
总结:
本题有两个关键点,第一个关键点是对于此类多个数进行异或的问题,我们可以考虑是否可以取出集合中所有1、10、100、1000这类数字。
第二个关键点是对于费马小定理的熟练掌握,对于指数上面还有指数的问题,不能直接用 p 去膜,而是要用费马小定理进行转换,从而求解。
如果此处 a和p 不互素的话,那就需要对费马小定理进行再次转换,请读者自己进行查阅。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define rep(i,a,b) for(int i = a;i <= b;i++)
using namespace std;
typedef long long LL;
LL n,k;
LL ans1 = 0;
LL poww(LL a,LL b,LL p)
{
LL base = a%p,ans = 1;
while(b!=0)
{
if(b&1)
ans = (ans*base)%p;
base = (base*base)%p;
b >>= 1;
}
return (ans%p);
}//快速幂
int main()
{
LL p;
while(~scanf("%lld%lld%lld",&n,&k,&p))
{
if(p == 2){
if(k == 0) printf("1\n");
else printf("0\n");
continue;
}
LL tmp = poww(2,n,p-1);
tmp = (tmp-n)%(p-1);
if(tmp < 0) tmp += p-1;
tmp = tmp%(p-1);
ans1 = poww(2,tmp,p);
ans1 = ans1%p;
if(k == 0) printf("%lld\n",(ans1-1+p)%p);
else printf("%lld\n",ans1);
}
return 0;
}
---------------------
作者:Gene_Liu_xe