[因子背包] CF1647D Madoka and the Best School in Russia

1 篇文章 0 订阅

由这次的D题从杜老师那学会的比较新奇的背包,我暂且将其命名为因子背包
题意:
一个数 x x x被称为好数,当且仅当他被 d d d整除而不被 d 2 d^2 d2整除。
给定一个数 n n n,问是否有超过两种组成方式,让它成为一个或若干个好数的乘积。
T T T组询问, T < = 100 T<=100 T<=100 n , d < = 1 0 9 n,d<=10^9 n,d<=109

Solution 1
首先定然可以很暴力的将所有 n n n的好数因子给找出来,我们需要的就是用这些数来构成 n n n
由于一个数可以用若干次,所以其实本质上这是一个完全背包问题,我们考虑正常的完全背包我们咋做的。
F [ i ] [ j ] = F [ i − 1 ] [ j ] + F [ i − 1 ] [ j / d [ i ] ] F[i][j]=F[i-1][j]+F[i-1][j/d[i]] F[i][j]=F[i1][j]+F[i1][j/d[i]],第一维可以滚掉,也就是 F [ j ] + = F [ j / d [ i ] ] F[j]+=F[j/d[i]] F[j]+=F[j/d[i]]
问题来了,由于值域太大,我们不可能遍历整个值域来做这个背包,但这时我们发现由于最后能对 F [ n ] F[n] F[n]做出贡献的只有 n n n的因子,而对 n n n的因子做出贡献的则是 n n n的因子的因子(也是 n n n的因子),因此我们只需要从小到大遍历 n n n的所有因子(最多也就 1300 1300 1300个左右),这样最终贡献给 F [ n ] F[n] F[n]的答案一定是正确的。

现在只剩下一个问题了,虽然我们只需要遍历 n n n的所有因子,但是这些因子可能也很大,并不能直接开一个这么大的数组。
我会map!
额,多了个 l o g log log,这题好像有可能会T。
那咋办?
可以用一个“双指针”的方法,我们开两个数组 i d [ 1 e 5 + 5 ] , g i d [ 1 e 5 + 5 ] id[1e5+5],gid[1e5+5] id[1e5+5],gid[1e5+5]
对于每一个因子 c [ i ] c[i] c[i],如果他小于等于 1 e 5 1e5 1e5,那么我们就将 i d [ c [ i ] ] = i id[c[i]]=i id[c[i]]=i,否则 g i d [ n / c [ i ] ] = i gid[n/c[i]]=i gid[n/c[i]]=i,这样就可以把值域全部映射回因子个数里,用的时候也判断一下要用的数是通过 i d id id还是 g i d gid gid存放的即可。

Code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#define ll long long
#define lowbit(x) x&(-x)
#define mp make_pair
#define rep(i,n) for(int i=1;i<=n;i++)
using namespace std;
const int mod=998244353;
const int maxn=1e7+5;
const int maxm=1e5;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=x*10+(c-'0');
		c=getchar();
	}
	return x*f;
}
int T;
int f[2050];
int id[100050],gid[100050];
int tot,c[2050];
signed main()
{
	T=read();
	while(T--)
	{
		tot=0;
		int n=read(),d=read();
		for(int i=1;i*i<=n;i++)
		{
			if(n%i==0) 
			{
				if(n/i==i) c[++tot]=i;
				else c[++tot]=i,c[++tot]=n/i;
			}	
		}
		sort(c+1,c+tot+1);
		for(int i=1;i<=tot;i++)
		{
			if(c[i]<=maxm) id[c[i]]=i;
			else gid[n/c[i]]=i;
		}
		f[1]=1;
		for(int i=1;i<=tot;i++)
		{
			if((c[i]%d==0)&&((c[i]/d)%d!=0))
			{
				for(int j=1;j<=tot;j++)
				{
					if((n/c[i])%c[j]==0)
					{
						int x=c[i]*c[j];
						int y=(x<=maxm)?id[x]:gid[n/x];
						f[y]=min(f[y]+f[j],2);
					}
				}
			}
		}
		if(f[tot]>=2) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;	
		for(int i=1;i<=tot;i++) f[i]=0;
	}
 } 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值