【Gym 101848】D.XOR 多数异或&&费马小定理

这个是转载的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 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值