hdu5514-Frogs(容斥原理)好题
题意:有m个石头围成一圈,编号分别为0到m-1,现在有n只青蛙,都在0号石头上,第i只青蛙会从当前编号为p的石头跳到编号为(p+ai)%m的石头上。被青蛙经过的石头都会被占领,求这m块石头中所有被占领过的石头的编号和。
题解:对于第i只青蛙,它所能跳到的最小的位置是gcd(ai, m)
设最小位置为z,需要跳x圈,跳了y步,可得方程:x*m+z=ai*y
即:x*m-ai*y = z 由扩展欧几里得定理可知,z的最小整数解为gcd(m,ai)
因为对于单独的每一只青蛙计算结果会重复计算,所以利用容斥对每一个m的因子计算。
首先对于每一个x=gcd(ai,m),如果m的一个因数fac%x==0,那么fac就会被跳到。
然后对于每一个会碰到的因数计算,当m的一个因数j的因数i被计算的时候,j就会被重复计算,要减去。
虽然题解很有道理,但是我想了好久也没明白容斥不是奇加偶减吗,怎么这么算了= =
后来搜题解明白了此题gcd太多,二进制枚举会爆longlong,dfs也会超时,http://www.acmtime.com/?p=864 一个神奇的剪枝。
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <cmath> using namespace std; const int N = 100005; int fac[N], cnt; int cc[N]; void cal(int x) { cnt = 0; int limit = sqrt(x); fac[cnt++] = 1; for (int i = 2; i < limit; ++i) { if (x % i == 0) fac[cnt++] = i, fac[cnt++] = x/i; } if (limit*limit == x) fac[cnt++] = limit; else if (x % limit == 0) fac[cnt++] = limit, fac[cnt++] = x/limit; sort(fac, fac+cnt); } int main() { int T, cas = 0; int n, m; scanf("%d", &T); while (T--) { printf("Case #%d: ", ++cas); scanf("%d%d", &n, &m); //1e4 1e9 cal(m); memset(cc, 0, sizeof cc); int ai; for (int i = 0; i < n; ++i) { scanf("%d", &ai); int gcd = __gcd(ai, m); for (int i = 0; i < cnt; ++i) { if (fac[i] % gcd == 0) cc[i] = 1; } } long long ans = 0; for (int i = 0; i < cnt; ++i) { if (cc[i] == 0) continue; long long tmp = (m-1) / fac[i]; ans += tmp * (tmp+1) / 2 * fac[i] * cc[i]; for (int j = i+1; j < cnt; ++j) { if (fac[j] % fac[i] == 0) cc[j] -= cc[i]; } } printf("%lld\n", ans); } return 0; }