jzoj2574 统计方案

31 篇文章 0 订阅

小B写了一个程序,随机生成了n个正整数,分别是a[1]..a[n],他取出了其中一些数,并把它们乘起来之后模p,得到了余数c。但是没过多久,小B就忘记了他选了哪些数,他想把所有可能的取数方案都找出来。你能帮他计算一下一共有多少种取数方案吗?请把最后的方案数模1000000007后输出。
小B记得他至少取了一个数。

对于30%的数据,n<=16;
另有30%的数据,p<=10000;
对于100%的数据,n<=32,p<=10^9,c<=10^9,a[i]

传说中的2^n算法能过30%.
另外百分之30考虑背包,用C作容量然后每一次取或不取

2^n大概能过n=20
考虑分治
先把n个数分成两组 (也就差不多一半,这个自己随便分,别超过20就好)
分开算每种余数方案数,用hash表存下 (STL黑科技map)

然后枚举一组的余数 假设是a
我们知道有ab=c(mod p)
转换为ab-kp=c用exgcd求解
解出b使得他在[0,p-1]这个区间内,这就是对应a的余数
然后将方案数一乘一加即可.

#include <cstdio>
#include <iostream>
#include <map> 
using namespace std;
long long n,na,p,c,ans,sox,soy;
map<long long,long long> ma,mb; //hashmap
long long a[100];
long long abs(long long x) {
    return(x>0)?x:-x;
}
void dfs(int x,int ys) {
    if (x>na) {
        if (ys==c) ans++;
        ma[ys]++;
        return;
    }
    dfs(x+1,ys*a[x]%p);
    dfs(x+1,ys);
}
void dfs2(int x,int ys) {
    if (x>n) {
        if (ys==c) ans++;
        mb[ys]++;
        return;
    }
    dfs2(x+1,ys*a[x]%p);
    dfs2(x+1,ys);
}
long long gcd(long long a,long long b) {
    return (b)?gcd(b,a%b):a;
}
void exgcd(long long a,long long b) {
    if (b==0) {
        sox=1;
        soy=0;
        return;
    }
    exgcd(b,a%b);
    int tmp=sox;
    sox=soy;
    soy=tmp-a/b*soy;
}
int main() {
    ma.clear();
    mb.clear();
    cin>>n>>p>>c;
    if(c>=p) {
        printf("0");
        return 0;

    }
    na=n/2;
    for (int i=1; i<=n; i++) cin>>a[i];
    for (int i=1; i<=na; i++) dfs(i+1,a[i]);
    for (int i=na+1; i<=n; i++) dfs2(i+1,a[i]);
    map<long long,long long>::iterator it; 

    for (it=mb.begin(); it!=mb.end(); it++) {
        int ys=it->first; 
        sox=soy=0;
        exgcd(ys, p);
        long long gc=gcd(ys,p);
        if (c%gc!=0) continue;
        sox=sox*(c/gc)%p;
        if (sox<0) sox+=abs(sox)/p*p;;
        if (sox<0) sox+=p;
        while (sox<p) {
            if (ma[sox]!=0) {
                ans=(ans+ma[sox]*it->second)%1000000007;
            }
            sox+=p;
        }
    }
    cout<<ans<<endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值