要计算几个集合并集的大小,我们要先将所有单个(奇数个)集合的大小计算出来,然后减去所有两个(偶数个)集合相交的部分,再加回所有三个(奇数个)集合相交的部分,再减去所有四个(偶数个)集合相交的部分…依此类推,一直计算到所有集合相交的部分。
euler(n) 小于n且与n互素的整数的个数
性质:
1.当a,b互质时 ∅ (a × b) = ∅ (a) × ∅ (b)(积性函数)
2.小于n且与n互素的整数之和为 ∅ (n) * n/2
不会证,但是观察发现假设∅ (n) = m那么第i个数和第m - i + 1个数的和等于n
3.当n>=3时,euler(n)一定是个偶数
题意:
有m个石子围成一圈(编号0~m-1), 有n只青蛙跳石子, 都从0号石子开始, 每只能越过xi个石子。问所有被至少踩过一次的石子的序号之和。
首先,需要知道对于一个步幅为x的青蛙,他能踩的石子编号是gcd(m,x)
的倍数(好像可以用exgcd证,但是多观察应该就能发现,然后大胆的瞎猜即可)。
有两种做法,容斥原理和欧拉函数。
对于一只步幅为x的青蛙,我们把所有gcd(x,m)
的倍数的石子加起来(等差数列求和),但这样有一个问题就是有的石子会被计算很多次。
我们需要考虑如何去重,即容斥原理。
我们可以记录一下x的倍数的石子应该被计算几次,记录完一只x的青蛙所踩的石子后,将因子里是x的倍数的数计算次数减去刚刚x的计算次数(这样更新的话,有的数的倍数需要计算的次数会变成负的,这样计算的时候就可以减去了)。(等差数列求完和后需要乘上对应的次数)。
需要维护的容器:
1.v[i]m的第i个因子,按顺序排列(注意m本身不是)
2.cnt[i] 因子v[i]的倍数需要计算的次数,初始时xi和m的gcd的倍数的cnt 值为1
AC代码:
#include
using namespace std;
typedef long long ll;
ll n, m, t;
ll ar[10005];
ll v[100005];
ll cnt[100005];
int tot;
ll cx;
bool flag;
ll ans, num;
inline ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a % b);
}
void make_arrv()
{
v[++tot] = 1;
cnt[tot] = 0;
for(ll i = 2; i <= sqrt(m); ++i)
{
if(m % i == 0)
{
v[++tot] = i;
cnt[tot] = 0;
if(i * i != m)
{
v[++tot] = m / i;
cnt[tot] = 0;
}
}
}
sort(v + 1, v + tot + 1);
}
void make_arrcnt(int k)
{
if(flag == 1) return ;
cx = gcd(ar[k], m);
//这有一个特判,如果有一个点gcd等于1,那么这只青蛙可以踩所有的石子,那就好办了,直接全部加起来即可
if(cx == 1)
{
flag = 1;
return ;
}
for(int i = 1; i <= tot; ++i)
{
if(v[i] % cx == 0) cnt[i] = 1;
}
}
int main()
{
scanf("%lld", &t);
for(int p = 1; p <= t; ++p)
{
tot = 0;
flag = 0;
ans = 0;
scanf("%lld%lld", &n, &m);
make_arrv();
cout << tot << endl;
for(int i = 1; i <= n; ++i)
{
scanf("%lld", &ar[i]);
make_arrcnt(i);
}
if(flag == 1)
{
ans = 1ll*(m - 1) * m / 2;
printf("Case #%d: %lld\n", p, ans);
continue;
}
for(int i = 1; i <= tot; ++i)
{
ans += m * (m / v[i] - 1) / 2 * cnt[i];
for(int j = i + 1; j <= tot; ++j)
{
if(v[j] % v[i] == 0) cnt[j] -= cnt[i];
}
}
printf("Case #%d: %lld\n", p, ans);
}
return 0;
}
大佬的做法:https://blog.nowcoder.net/n/fbc059cfa58245c68a0b5d4ec2a02984?f=comment
AC代码:
#include
using namespace std;
typedef long long ll;
const int maxn = (int)1e4 + 5;
ll gcc[maxn];
ll t, n, m, ans;
ll x;
bool flag;
inline ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a % b);
}
bool judge(ll x)
{
for(int i = 1; i <= n; ++i)
{
if(x % gcc[i] == 0) return true;
}
return false;
}
ll euler(ll n)
{
ll res = n;
for(ll i = 2; i * i <= n; ++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 main()
{
scanf("%lld", &t);
for(int p = 1; p <= t; ++p)
{
flag = 0;
ans = 0;
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++i)
{
scanf("%lld", &x);
gcc[i] = gcd(x, m);
if(gcc[i] == 1) flag = 1;
}
if(flag)
{
printf("Case #%lld: %lld\n", p, m * (m - 1) / 2);
continue;
}
for(ll i = 2; i <= sqrt(m); ++i)
{
if(m % i == 0)
{
if(judge(i)) ans += euler(m / i) * m / 2;
if(i * i != m && judge(m / i)) ans += euler(i) * m / 2;
}
}
printf("Case #%d: %lld\n", p, ans);
}
return 0;
}
然后这个题,都要处理m的因子,并且都需要多次遍历v[]数组,当时还在害怕数组开的小了,时间过不去。。。后来查了一下。。。根本没那么多因子。。。