1. J-Product of GCDs_2021牛客暑期多校训练营2 (nowcoder.com)
给定一个集合,求其所有大小为 k k k 的子集的 g c d gcd gcd 之积. 子集的 g c d gcd gcd 定义为子集所有元素的最大公因数.
思路很简单,每个质因数都分开考虑. 因为 p p p 不一定是质数所以要求欧拉函数降幂。由于 n ∗ k n*k n∗k 很小所以可以 O ( n k ) O(nk) O(nk) 递推法求组合数.
这个题卡线性,有一个做法是保存至少含有 p c p^c pc 的数字有多少个,记为 c n t ( p , c ) cnt(p, c) cnt(p,c),然后这样的方案数是 C c n t ( p , c ) k C_{cnt(p, c)}^{k} Ccnt(p,c)k,每个质数把幂次从大到小枚举,容斥一下。这样质数的个数是 n log n \frac{n}{\log n} lognn,质数的幂是 log n \log n logn. 因此枚举一遍是 O ( n ) O(n) O(n)
这里放一个超时的板子,希望能够改一改卡过去.
#include<bits/stdc++.h>
using namespace std;
const int N = 80010;
typedef long long ll;
int prime[N], cnt, st[N];
ll C[40][40010], f[N];
int n, k, a[N];
ll phi, ans, P;
typedef pair<int, int> PII;
#define x first
#define y second
vector<PII> divs[N];
ll mod_pow(ll x, ll n, ll mod)
{
ll res = 1;
while(n)
{
if(n & 1) res = (__int128)res * x % mod;
x = (__int128)x * x % mod;
n >>= 1;
}
return res;
}
void sieve(int n)
{
st[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!st[i]) st[i] = i, prime[cnt++] = i;
for(int j = 0; prime[j] <= n / i; j++)
{
st[i * prime[j]] = prime[j];
if(i % prime[j] == 0) break;
}
}
for(int i = 1; i <= n; i++)
{
int m = i;
while(m > 1)
{
ll cnt = 0, p = st[m];
while(m % p == 0)
{
m /= p;
cnt++;
}
divs[i].push_back({p, cnt});
}
}
}
ll get_phi(ll n)
{
ll res = n;
for(ll i = 2; i <= n / i; i++)
{
if(n % i == 0)
{
res = res / i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1) res = res / n * (n - 1);
return res;
}
int CNT[N][20];
void solve(ll p)
{
CNT[p][0] = n;
for(int i = 1; i < 20; i++) CNT[p][0] -= CNT[p][i];
if(CNT[p][0] == n) return;
int remain = n;
for(int i = 0; i < 20; i++)
{
if(remain < k) break;
ans = (__int128)ans * mod_pow(p, (__int128)i * (f[remain - 1] -
((remain - CNT[p][i] - 1) < 0 ? 0 : (f[remain - CNT[p][i] - 1])) + phi) % phi, P) % P;
remain -= CNT[p][i];
}
}
int main()
{
sieve(N - 1);
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d%d%lld", &n, &k, &P);
ans = 1;
fill(f, f + n + 1, 0);
for(int i = 0; i < cnt; i++) fill(CNT[prime[i]], CNT[prime[i]] + 20, 0);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
phi = get_phi(P);
for(int i = 0; i < n; i++)
{
int sz = min(i, k);
for(int j = 0; j <= sz; j++)
{
if(j == 0) C[j][i] = 1;
else C[j][i] = (C[j][i - 1] + C[j - 1][i - 1]) % phi;
}
}
f[k - 1] = 1;
for(int i = k; i <= n; i++) f[i] = (f[i - 1] + C[k - 1][i]) % phi;
for(int i = 1; i <= n; i++)
{
for(auto p : divs[a[i]]) CNT[p.x][p.y]++;
}
for(int i = 0; i < cnt; i++)
{
solve(prime[i]);
}
printf("%lld\n", ans);
}
return 0;
}
2. H-Convolution_2021牛客暑期多校训练营4 (nowcoder.com)
比较难
3. G-Greater Integer, Better LCM_2021牛客暑期多校训练营5 (nowcoder.com)
比较难
4. Pass! - HDU 6956 - Virtual Judge (vjudge.net)
BSGS+卡常
5. I love permutation - HDU 6970 - Virtual Judge (vjudge.net)
6. Problem - 1548B - Codeforces
在一个序列上找到最长的连续子区间,使得存在 m ≥ 2 m \ge 2 m≥2,子区间内所有数字模 m m m 同余.
如果每个数字模 m m m 的值都一样,设模数为 d d d,那么说明 a i − d a_i - d ai−d 模 m m m 都为 0 0 0,说明 gcd ( a − d , b − d ) = gcd ( a − d , ∣ a − b ∣ ) > 1 \gcd(a-d, b-d) =\gcd(a-d, |a - b|) > 1 gcd(a−d,b−d)=gcd(a−d,∣a−b∣)>1. 就是在差分数组上找到最长的一段子区间他们的最大公约数不为 1 1 1. 然后答案就是这个区间的长度加1. 考虑到最大公约数的单调性用双指针即可.
7. Problem - 1547F - Codeforces
给一个序列环 a a a ( a n − 1 a_{n-1} an−1 与 a 0 a_0 a0 相接),每次可以用一个新的序列 b b b 替代,即 b i = gcd ( a i , a i + 1 m o d n ) b_i = \gcd(a_i, a_{i + 1\ \bmod\ n}) bi=gcd(ai,ai+1 mod n),问最少需要替换多少次才能使这个序列的所有数字都是一样的.
观察可以发现,设原始序列所有数字的最大公约数为 d d d,那么所有数字最终都会变成 d d d,并且 a i a_i ai 变化为 d d d 的次数是从 a i a_i ai 开始能够延伸到最短的距离,且当前的子区间的最大公约数为 d d d. 至于环怎么处理,把这个序列复制一遍接在后面变成之前的两倍长度即可.
8. Problem - 1537D - Codeforces
给一个正整数 n ( n ≤ 1 0 9 ) n(n \le 10 ^ 9) n(n≤109),两个人轮流玩游戏,每次进行的操作是选 n n n 的一个因数 d d d,要求 d ≠ 1 d \ne 1 d=1 且 d ≠ n d \ne n d=n. 不能操作的人输掉。问最后谁会赢.
打个表,规律显然。当 n n n 是奇数,或者 n n n 是2的奇数次幂的时候,先手必输。其他情况先手必赢。可以这样子分析:
总共有三种情况:
- n n n 是奇数
- n n n 是偶数并且不是 2 2 2 的幂
- n n n 是 2 2 2 的幂
第一种情况:如果 n n n 是质数就输了。否则只能减去一个奇数,变为第二种情况.
第二种情况:可以减去一个奇数变成第一种情况,然后对手在操作一步变成第二种情况. 直到丢给对手一个奇质数为止。因此这种情况必赢,而第一种情况必输.
第三种情况,只有将 n n n 减半,才能避免对手进入第二种情况。因此只有当前的幂次是偶数的时候才能赢.