[一本通1681]统计方案 题解(Meet in mid与逆元的结合)

题目描述

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

输入

第一行三个正整数\(n,p,c\),含义如题目所述。
接下来一行有\(n\)个正整数,表示生成的\(n\)个随机数。

输出

一行一个数,方案数模\(1000000007\)

输入样例

2 7 2
1 2

输出样例

2

提示

数据规模与约定

  1. 对于30%的数据,\(n≤16\)
  2. 另有30%的数据,\(p≤10000\)
  3. 对于100%的数据,\(n≤32,p≤10^9,c≤10^9,a[i]\lt p\),其中\(p\)是质数。

思路

如果采用传统暴力搜索\(2^{32}\)肯定受不了。由于这个复杂度是由\(2\)为底,所以可以试一下Meet in mid算法。复杂度是\(O(2\times 2^{16})\),可以通过。
首先Dfs把前16个数字所有的情况存起来,我这里用的是\(HashMap\)存起来。
再利用第二次Dfs把后16个数字的情况算出来,同时找出对应的前16个数字,使得\(a \times b\mod p=c\)这里我们知道a,求b。根据费马小定理,推导可以知道

\[b=a^{p-2}\times c\mod p \]

这里还可以用快速幂来优化。
当然有些情况需要特判。在代码里会说明。

代码

#include <iostream>
#include <tr1/unordered_map>
#include <cstdio>
using namespace std;
using namespace tr1;

typedef long long ll;

const int N = 33;
const ll MOD = 1e9 + 7;

ll num[N];

unordered_map<ll, int> M;

int n, c;
ll p;

inline ll Qpow(ll a, ll n, ll mod)
{
    ll ans = 1;
    while (n)
    {
        if (n & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return ans;
}

void dfs1(int u, ll times)
{
    if (u > n / 2)
    {
        M[times]++;
        return;
    }

    dfs1(u + 1, times * num[u] % p);
    dfs1(u + 1, times);
}

ll cnt = 0;

void dfs2(int u, ll times)
{
    if (u > n)
    {
        cnt = (cnt + M[c * Qpow(times, p - 2, p) % p]) % MOD;
        return;
    }
    dfs2(u + 1, times * num[u] % p);
    dfs2(u + 1, times);
}

int main()
{
    cin >> n >> p >> c;
    if (c >= p) //不合理的情况
    {
        printf("0");
        return 0;
    }
    for (int i = 1; i <= n; i++)
        cin >> num[i];
    dfs1(1, 1);
    dfs2(n / 2 + 1, 1);
    if (c == 1) //会把不选数统计进去
        cnt--;
    printf("%lld", cnt);
    return 0;
}
posted @ 2021-08-12 13:54  Icys  阅读(216)  评论(0编辑  收藏  举报