信息学奥赛一本通 高手训练1 统计方案数

统计方案

【题目描述】

小B写了一个程序,随机生成了n个正整数,分别是a[1]…a[n],他取出了其中一些数,并把它们乘起来之后模p,得到了余数c。但是没过多久,小B就忘记了他选了哪些数,他想把所有可能的取数方案都找出来。

你能帮他计算一下一共有多少种取数方案吗?请把最后的方案数模1000000007后输出。

小B记得他至少取了一个数。

【输入】

第一行三个正整数n,p,c,含义如题目所述。

接下来一行有n个正整数,表示生成的n个随机数。

【输出】

一行一个数,方案数模1000000007。

【输入样例】

2 7 2
1 2

【输出样例】

2

【提示】

【数据规模与约定】

对于30%的数据,\(n≤16\)

另有30%的数据,\(p≤10000\)

对于100%的数据,\(n ≤ 32\)\(p≤10^9\)\(c ≤ 10 ^ 9\)\(a[i]<p\),其中 \(p\) 是质数。

【思路】

【题目重点】

先分析一下数据范围,30%的数据是 \(n <= 16\) 的,而100%的数据 \(n <= 32\),看起来都是非常的小,很容易想到用搜索来做(而且分类就在深搜里面,所以我好像说的是废话)
看完 \(n\) 之后再来看 \(p\) , 对于30%的数据 \(p <= 10000\), 而对于100%的数据\(p <= 10 ^ 9\),这样很显然也是必须要用 long long 的,要不然要这么大的 \(p\) 没有什么用。
然后就可以开始做题了。
如果这是一个正常的搜索的话 \(n <= 32\) 是可以用什么记忆化搜索呀,什么优美的剪枝呀可以过掉的,但是这道题目貌似没有办法使用剪枝这一类的优化。
因为如果你这样搜索,搜索到了某几个数乘起来%\(p\)之后刚好等于\(c\),可是这个时候是不能像其他的搜索一样直接 \(ans\) ++ 然后return继续搜索的,因为你没法保证后面不会还出现在这种情况的前提下在多乘上几个数就可以有出现一种% \(p\) 刚好等于 \(c\) 的情况,所以还得继续搜索下去,没有办法直接return。
已知这些直接,加上为了避免重复枚举的时候只枚举这个数之后的数,不枚举他前面的数,这样复杂度就很显然了\(O(n!)\)
【小提示】16! = 20922789888000
最朴素的做法就是直接爆搜了,一个dfs搜索每一种可能出现的情况,这样就很容易爆了,也就刚刚能拿到30分

【正确解法】

\(O(n!)\)\(n = 16\) 的时候不会爆,但是当 \(n = 32\) 的时候却会爆,所以怎么让\(32!\)不会爆炸呢!?
很显然,答案就是把 \(n = 32\) 分为两个 \(n = 16\) 来搜索就好了。
用map + vector卡过去了
将n分成两块\(1 - n / 2\)\(n / 2 + 1 -n\)分别搜索然后都用一个桶将可能出现的不是答案的情况记录下来
如果是答案的话那就直接 \(ans\) 加上就好了
将两部分的搜索完成之后
再枚举两块的搭配方式看看有没有可能出现答案,出现那就 \(ans\) 加上能够出现的可能 。

30分代码
#include<iostream>
#include<cstdio>
#define int long long

using namespace std;
const int Max = 35;
int n,p,c;
int a[Max];
bool use[Max];
int ans = 0;

void search(int x,int mo,int jss)
{
	if(mo == c && jss != 0)
		ans ++;
	if(jss == n)
		return;
	for(int i = x;i <= n;++ i)
	{
		if(use[i] == false)
		{
			use[i] = true;
			search(i + 1,(mo * a[i]) % p,jss + 1);
			use[i] = false;
		}
	}
}

signed main()
{
	scanf("%lld%lld%lld",&n,&p,&c);
	if(c >= p)
	{
		cout << 0 << endl;
		return 0; 
	 } 
	for(int i = 1;i <= n;++ i)
		scanf("%lld",&a[i]);
	search(1,1,0);
	cout << ans << endl;
}
100分代码
#include<iostream>
#include<cstdio>
#include<map>
#include<vector>
#define int long long
#define mm 1000000007
using namespace std;
const int Max = 35;
map<int,int> c1,c2;
vector<int> acioi;
int n,p,c;
int m;
int f[Max];
int ans = 0;

void dfs(int x,int mo,int jj)
{
	if(jj == n / 2)
	{
		c1[mo] ++;
		if(c1[mo] == 1)acioi.push_back(mo);
	}
	else
	{
		c2[mo] ++;
		if(c2[mo] == 1)acioi.push_back(mo);
	}
	if(mo == c) ans = (ans + 1) % mm;
	for(int i = x + 1;i <= jj;++ i)dfs(i,(mo * f[i]) % p,jj);
}

signed main()
{
	scanf("%lld%lld%lld",&n,&p,&c);
	for(int i = 1;i <= n;++ i)
		scanf("%lld",&f[i]);
	if(c >= p)
	{
		cout << 0 << endl;
		return 0;
	}
	m = n / 2;
	for(int i = 1;i <= m;++ i)dfs(i,f[i],m);
	int mid = acioi.size();
	for(int i = m + 1;i <= n;++ i)dfs(i,f[i],n);
	for(int i = 0;i < mid;++ i)
		for(int j = mid;j < acioi.size();++ j)
			if( (acioi[i] * acioi[j]) % p == c )
				ans = (ans + c1[acioi[i]] * c2[acioi[j]]) % mm;
	cout << ans << endl;
	return 0;
}

【有趣的东西】

最后说一点和本题无关的东西,这个题我是在一本通那个评测网站上做的,高手训练1里面的,同时在高手训练1另一个分类 广搜 里面,有一道题目叫做积水问题,这道题是可以通过上面的那个部分分代码和AC代码都是可以得到5分的,20个数据点嘻嘻,我也是不经意之间发现的\(QWQ\)信息学奥赛一本通 高手训练1 游戏通关

posted @ 2019-09-22 15:27  acioi  阅读(732)  评论(0编辑  收藏  举报