题目
一个集合里的数为 1 − n 1-n 1−n,选择 x ( 1 ⩽ x ⩽ k ) x(1\leqslant x \leqslant k) x(1⩽x⩽k)个数,使得这 x x x个数的乘积不能被任何除1之外的所有平方数整除。
样例
Input: Output:
2(T)
4 2(n=4,k=2) 6 (1,2,3,[1,2],[1,3],[2,3])
6 4 19
思路
首先说明这道题我是看的qy的代码,没有仔细研究过别人的思路,所以我不知道我说的对不对……
- 首先本身是平方数倍数的数肯定不能取,如test1的4.然后考虑 x x x个数之积满足不是平方数倍数的条件是什么?
这 x x x个数之积不含有相同的质因子。
- 我们如何去判断这 x x x个数是否含有相同的质因子?
参考状压的思想,一个质因子存不存在可以转化为一个2进制数该位是为1还是为0。所以我们把1到 n n n所有数枚举质因数,处理出这个数的质因数所对应的一个 s t a t e state state.
- d p ( i , j , s ) dp(i,j,s) dp(i,j,s)表示我们枚举到 i i i时取了 j j j个数时的状态是 s s s时的方案数.
状态转移方程: d p ( i , j , s ) = d p ( i − 1 , j , s ) + d p ( i − 1 , j − 1 , l a s t s t a t e ) dp(i,j,s)=dp(i-1,j,s)+dp(i-1,j-1,last state) dp(i,j,s)=dp(i−1,j,s)+dp(i−1,j−1,laststate)
- 然后我们发现方程逆推的话没有正推方便,即我们用小状态去推大状态
d p ( i + 1 , j , s ) + = d p ( i , j , s ) ( 第 i + 1 个 没 有 选 ) dp(i+1,j,s) += dp(i,j,s) (第i+1个没有选) dp(i+1,j,s)+=dp(i,j,s)(第i+1个没有选)
当没有质因子重复时即 s s s& w = = 0 w==0 w==0时, d p ( i + 1 , j + 1 , s ∣ w ) + = d p ( i , j , s ) ( 第 i + 1 个 选 了 , 并 且 第 i + 1 的 状 态 为 w ) dp(i+1,j+1,s|w) += dp(i,j,s) (第i+1个选了,并且第i+1的状态为w) dp(i+1,j+1,s∣w)+=dp(i,j,s)(第i+1个选了,并且第i+1的状态为w)
-
到这里基本思想整理的差不多了,但是实际上离写题还很远。
-
考虑 d p ( i , j , s ) dp(i,j,s) dp(i,j,s)的最后一维,这个 s s s的大小是取决于500以内的质数数量的。很明显如果全部存下来肯定会MLE和TLE。
任何一个数只有一个大于 n \sqrt{n} n的质因数,所以一方面我们只枚举 500 \sqrt{500} 500的质数
int prime[8] = {2, 3, 5, 7, 11, 13, 17, 19};
,另一方面按照数除尽这些质数剩下来的因数分组。
代码实现:开一个 v e c t o r vector vector数组, A [ i ] A[i] A[i]存放最后质数或者1为i的所有 s t a t e state state。同时将本身是平方数倍数的数去除。
void divide_factor(int x)
{
int state = 0;
for (int i=0; i<8; i++)
{
if (x % prime[i] == 0)
{
x /= prime[i];
if (x % prime[i] == 0)
return;
state |= (1 << i);
}
}
A[x].push_back(state);
}
- 分组之后考虑: A [ 1 ] A[1] A[1]里的 s t a t e state state是任意取的,类似一个01背包。而 A [ i ] ( i > 1 ) A[i](i>1) A[i](i>1)的 s t a t e state state却不是任意取的,即每组只能取一个,是一个分组背包。突然放一个背包九讲链接,呵呵
- 这里假设我们用三维的状态,即上述的 d p ( i , j , k ) dp(i,j,k) dp(i,j,k),我们写个代码:
memset(dp, 0, sizeof(dp));
dp[0][0][0] = 1;
for (int i=0; i<A[1].size(); i++) //枚举物品
{
int w = A[1][i];
for (int j=0; j<=i; j++) //枚举到i最多可以取i个物品
for (int s=0; s<(1<<8); s++)
{
dp[i+1][j][s] += dp[i][j][s];
if ((s & w) == 0)
{
dp[i+1][j+1][s|w] += dp[i][j][s];
}
}
}
如果不考虑内存大小的问题,这个代码会不会有问题?
问题出在这个 i i i上面,这个 i i i表示的并不是枚举到所有数的第 i i i个数,而是最后余下为1的数中的第 i i i个数,也就是说,对应每个 A [ x ] A[x] A[x]都可能有 i i i,所以这个 i i i会互相覆盖……
- 那有没有什么办法避免这个问题?对 i i i进行处理,或者直接不要这个 i i i?
01背包的一维数组就可以用啦,即逆序枚举 j j j,然后更新数组……
for (int i=0; i<A[1].size(); i++)
{
int w = A[1][i];
for (int j=k-1; j>=0; j--) //`w` can be taken for 1-k times
for (int s=0; s<(1<<8); s++) //enumerate the states
{
if (dp[j][s])
{
if ((s & w) == 0)
dp[j + 1][s | w] = (dp[j + 1][s | w] + dp[j][s]) % mod;
}
}
}
- 最后就是剩下 A [ x ] ( x > 1 ) A[x](x>1) A[x](x>1)的情况了,这里是分组背包。
f [ k ] [ v ] = m a x ( f [ k − 1 ] [ v ] , f [ k − 1 ] [ v − c [ i ] ] + w [ i ] ∣ 物 品 i 属 于 第 k 组 ) f[k][v]=max(f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组) f[k][v]=max(f[k−1][v],f[k−1][v−c[i]]+w[i]∣物品i属于第k组)
一维数组伪代码:
for 所有的组k
for v=V..0
for 所有的i属于组k //这里是重点,把物品放到最内层枚举
f[v]=max{f[v],f[v-c[i]]+w[i]}
然后我们套到这道题,感觉代码排版略丑……
for (int i=2; i<=n; i++)
{
if (A[i].size() > 0)
{
for (int j=k-1; j>=0; j--)
{
for (int s=0; s<(1<<8); s++)
{
if (dp[j][s])
{
for (int p=0; p<A[i].size(); p++)
{
int w = A[i][p];
if ((s & w) == 0)
dp[j + 1][s | w] = (dp[j + 1][s | w] + dp[j][s]) % mod;
}
}
}
}
}
}
- 然后我思考了一下为什么分组背包是把物品枚举放到里面,而01是放外面的。
假设一组里的状态为 w 1 , w 2 … w n w_1,w_2\dots w_n w1,w2…wn,如果把这个循环放外面,可能存在情况某个状态 s = s ′ ∣ w 1 ∣ w 2 … ∣ w n s=s'|w_1|w_2\dots|w_n s=s′∣w1∣w2…∣wn的情况。但是如果放里面可以确保一个 s s s,肯定是 s ′ ∣ w 1 , s ′ ∣ w 2 , … , s ′ ∣ w n s'|w_1,s'|w_2,\dots,s'|w_n s′∣w1,s′∣w2,…,s′∣wn的某一种。恩……我是这样感觉的……
- 最后结果就是就是枚举选的个数和状态所有 d p [ j ] [ s ] dp[j][s] dp[j][s]的方案总数啦。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
typedef long long llong;
const int maxn = 500 + 10;
const int mod = 1e9 + 7;
int prime[8] = {2, 3, 5, 7, 11, 13, 17, 19};
vector <int> A[maxn];
int dp[maxn][(1<<8) + 100];
int n, k;
void divide_factor(int x)
{
int state = 0;
for (int i=0; i<8; i++)
{
if (x % prime[i] == 0)
{
x /= prime[i];
if (x % prime[i] == 0)
return;
state |= (1 << i);
}
}
//printf("x=%d state=%d\n", x, state);
A[x].push_back(state);
}
void solve()
{
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for (int i=0; i<A[1].size(); i++)
{
int w = A[1][i];
for (int j=k-1; j>=0; j--) //w can be taken for once
for (int s=0; s<(1<<8); s++) //enumerate the status
{
if (dp[j][s])
{
if ((s & w) == 0)
dp[j + 1][s | w] = (dp[j + 1][s | w] + dp[j][s]) % mod;
}
}
}
for (int i=2; i<=n; i++)
{
if (A[i].size() > 0)
{
for (int j=k-1; j>=0; j--)
{
for (int s=0; s<(1<<8); s++)
{
if (dp[j][s])
{
for (int p=0; p<A[i].size(); p++)
{
int w = A[i][p];
if ((s & w) == 0)
dp[j + 1][s | w] = (dp[j + 1][s | w] + dp[j][s]) % mod;
}
}
}
}
}
}
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &n, &k);
for (int i=1; i<=n; i++)
A[i].clear();
for (int i=1; i<=n; i++)
divide_factor(i);
solve();
int ans = 0;
for (int i=1; i<=k; i++)
for (int j=0; j<(1<<8); j++)
{
if (dp[i][j])
ans = (ans + dp[i][j]) % mod;
}
printf("%d\n", ans);
}
return 0;
}