题目
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5514
题目来源:2015沈阳区域赛,现场A的题,银牌题。
简要题意:n个青蛙在m长的环上从0开始无限跳,每只跳ai远,求所有会被青蛙跳到的格子下标之和。
数据范围:T⩽20;1⩽n⩽104;1⩽m⩽109;ai⩽109
显然很容易看出格子是否被跳和gcd(ai,m)有关。
对于任意一个gcd(ai,m),其[0,m)内的倍数一定会被跳到。
这个命题进行转化就是对于一个格子x,如果存在gcd(ai,m)∣x,那它会被跳到。
由于gcd(x,m)∣m,其规模最大为m的约数。
gcd(ai,m)∣gcd(x,m)是x被跳到的充要条件。
于是我们可以枚举m的约数g,让gcd(x,m)=g的数形成一个集合,通过gcd(ai,m)∣g的存在性来判断该集合的数是否该被加入。
如果加入的话,由欧拉函数就可以得到集合元素的和了。
比x小与之互质的数的和为φ(x)x2(注意是小,不是小于等于,小于等于要特判1,钢霸发现这个点的)。
于是结果为gφ(m/g)m/g2=φ(m/g)m2。
注意g=m的情况需要去掉,因为是[0,m)的。
实现
基本上怎么写都是过,沈阳的时候随手写了发很暴力的欧拉和枚举,都过了。
回来尝试了下分解素因数再枚举约数,结果没有快非常多。
会写欧拉的话实现这道题不是很难,还可以用容斥原理写
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
LL powmod(LL a,LL b, LL MOD) {LL res=1;a%=MOD;for(;b;b>>=1){if(b&1)res=res*a%MOD;a=a*a%MOD;}return res;}
// head
const int N = 1E4+5;
int t, n, m, cas = 1;
int a[N];
LL getPhi(int m) {
int phi = m;
for (int i = 2; i*i <= m; i++) {
if (m % i == 0) {
while (m % i == 0) m /= i;
phi = phi/i*(i-1);
}
}
return m > 1 ? phi/m*(m-1) : phi;
}
bool ck(int x) {
for (int i = 0; i < n; i++) {
if (x % a[i] == 0) return true;
}
return false;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%d", a+i);
a[i] = __gcd(a[i], m);
}
LL ans = 0;
for (int i = 1; i*i <= m; i++) {
if (m % i) continue;
if (ck(i)) ans += (LL)getPhi(m/i)*m/2;
if (i*i == m || i == 1) continue;
if (ck(m/i)) ans += (LL)getPhi(i)*m/2;
}
printf("Case #%d: %I64d\n", cas++, ans);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const LL INF = 1e17+5;
const LL MAXN = 1e6+5;
const int MOD = 1e9+7;
const double eps = 1e-7;
const double PI = acos(-1);
using namespace std;
LL a[MAXN], tmp[MAXN];
LL vis[MAXN], num[MAXN];///vis:标记可以走哪些因子数 num[i]:第i个因子加了几次。
LL GCD(LL a, LL b){
if(b == 0)
return a;
return GCD(b, a%b);
}
int main()
{
LL T;
scanf("%lld",&T);
for(LL cas=1; cas<=T; cas++){
LL n, m, cnt = 0;
scanf("%lld%lld",&n,&m);
for(LL i=1; i*i<=m; i++){
if(m%i==0){
if(i*i != m)
tmp[cnt++] = m/i;
tmp[cnt++] = i;
}
}
sort(tmp, tmp+cnt);
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
for(LL i=0; iscanf("%lld",&a[i]);
for(LL i=0; ifor(LL j=0; j1; j++)///计算到 cnt-1 就行了
if(tmp[j] % x == 0)
vis[j] = 1;
}
LL ans = 0;
/**考虑每个因子的贡献是:(m/tmp[i])*(m/tmp[i]-1)/2*tmp[i],等差数列求和
首先将因子 tmp[i] 提出来
m/tmp[i]:相当于等差数列的 a1 + an;
m/tmp[i]-1: 这是等差数列的个数
**/
for(LL i=0; i1; i++){
if(num[i] != vis[i]){
///先计算 vis[i] - num[i] == 1 的
ans += (m/tmp[i])*(m/tmp[i]-1)/2*tmp[i]*(vis[i]-num[i]);
///cout<<"ans = "<
LL tp = vis[i] - num[i];
for(LL j=i; j1 ; j++)
if(tmp[j] % tmp[i] == 0)
num[j] += tp;
}
}
printf("Case #%lld: %lld\n",cas,ans);
}
return 0;
}