乘积
题目背景
(mathrm{Smart}) 最近在潜心研究数学, 他发现了一类很有趣的数字, 叫做无平方因子数。 也就是这一类数字不能够被任意一个质数的平方整除, 比如(6)、(7)、(10)都是无平方因子数, 而(12)则不是。
题目描述
所以 (mathrm{Smart}) 在思考一个问题——选择不超过 (K) 个 (N) 以内的正整数乘起来, 使得乘积是一个无平方因子数, 有多少种取法? (每个数只能取一次)
输入输出格式
输入格式
第一行一个整数 (T) 表示数据组数。
接下来 (T) 行, 每行两个整数 (N),(K), 意思如题面所述。
输出格式
对于每一组数据, 输出一个整数表示取法的方案数对 (10^9+7) 取模后的数值。
说明
(10\%)的数据: (N≤8);
(40\%)的数据: (N≤16);
(70\%)的数据: (N≤30);
(100\%)的数据: (1≤T≤5); (1≤K≤N≤500)。
70pts 有非常多种搞法,然而状压是最难写的但是最可能继续玩出正解的。。
可是我太菜,比赛时写了个麻烦的状压
(dp[i][j][s])代表前(i)个数选择了(j)个素数状态为(s)的方案数
Code:
#include <cstdio>
#include <cstring>
const int N=502;
int num[30][10],dat[30];
int pri[N],is[N],v[N],cnt,tot;
int div[N][100];
void init()
{
for(int i=2;i<=500;i++)
{
if(!is[i])
{
pri[++cnt]=i;
v[i]=i;
}
for(int j=1;j<=cnt&&i*pri[j]<=500;j++)
{
if(v[i]<pri[j]) break;
v[i*pri[j]]=pri[j];
is[i*pri[j]]=1;
}
}
for(int i=2;i<=500;i++)
{
int t=i;
for(int j=1;j<=cnt;j++)
while(t%pri[j]==0)
div[i][j]++,t/=pri[j];
}
for(int i=2;i<=33;i++)
{
int flag=1;
for(int j=1;j<=12;j++)
if(div[i][j]>1) {flag=0;break;}
if(!flag) continue;
++tot;
dat[tot]=i;
for(int j=1;j<=12;j++)
if(div[i][j])
num[tot][j]=1;
}
}
int mod=1e9+7;
int n0,k;
int dp[20][20][1200];
void work()
{
scanf("%d%d",&n0,&k);
int l=0,n=0;
for(int i=1;;i++)//素数长度上界
{
if(pri[i]<=n0) ++l;
else break;
}
for(int i=1;;i++)//选数个数上界
{
if(dat[i]<=n0) ++n;
else break;
}
k=(k<=n?k:n);
memset(dp,0,sizeof(dp));
for(int i=0;i<=n;i++) dp[i][0][0]=1;
int ans=0;
for(int i=1;i<=n;i++)//前i个数
for(int j=1;j<=k;j++)//取了j个
for(int s=1;s<1<<l;s++)//素数集合状态
{
int las=s,flag=1;
dp[i][j][s]=dp[i-1][j][s];
for(int q=1;q<=l;q++)
if(num[i][q])//如果这一位是1
{
if((s>>q-1)&1) las^=1<<q-1;//变成0
else {flag=0;break;}
}
if(flag) (dp[i][j][s]+=dp[i-1][j-1][las])%=mod;
}
for(int i=1;i<=k;i++)
for(int s=1;s<1<<l;s++)
{
if(i!=k) (ans+=dp[n][i][s]<<1)%=mod;
else (ans+=dp[n][i][s])%=mod;
}
printf("%d
",ans+1);
}
int main()
{
freopen("mult.in","r",stdin);
freopen("mult.out","w",stdout);
init();
int t;
scanf("%d",&t);
while(t--)
work();
return 0;
}
而正解只是运用分组背包的思想
注意到大于19的质数只可能出现一个
那我们实际上就只需要找到状压2,3,5,7,11,13,17,19这几个素数就行了
其他大于19的素数按照这个素数进行分组,剩下的同时按前几个素数做就可以了
很巧妙的思想,然而数据不好造,答案一样的期望炒鸡高
Code:
#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long
const int N=500;
const ll mod=1e9+7;
const int pri[9]={0,2,3,5,7,11,13,17,19};
using namespace std;
vector <int > g[N+10];
int belong[N+10],sta[N+10],n,k;
void init()
{
memset(belong,0,sizeof(belong));
memset(sta,0,sizeof(sta));
for(int i=1;i<=N;i++) g[i].clear();
for(int i=1;i<=n;i++)
{
belong[i]=i;
for(int j=1;j<=8;j++)
{
if(i%(pri[j]*pri[j])==0) {sta[i]=-1;break;}
else if(i%pri[j]==0) {belong[i]/=pri[j];sta[i]|=1<<j-1;}
}
}
for(int i=1;i<=n;i++)
{
if(~sta[i])
{
if(belong[i]!=1)
g[belong[i]].push_back(sta[i]);
else
g[i].push_back(sta[i]);
}
}
}
ll dp[N+10][256];
void work()
{
scanf("%d%d",&n,&k);
init();
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=k;j;j--)
for(int s=255;~s;s--)
{
if(!dp[j][s]) continue;
for(int l=0;l<g[i].size();l++)
{
int now=g[i][l];
if(now&s) continue;
(dp[j][now|s]+=dp[j-1][s])%=mod;
}
}
}
ll ans=0;
for(int i=1;i<=k;i++)
for(int s=0;s<=255;s++)
(ans+=dp[i][s])%=mod;
printf("%lld
",ans);
}
int main()
{
//freopen("mult.in","r",stdin);
//freopen("mult.out","w",stdout);
int t;scanf("%d",&t);
while(t--)
work();
return 0;
}
2018.8.20