题目大意
编号为0-m-1的石头围成一圈,有n个青蛙,每个青蛙从0开始每次严格跳ai个石头,问总的被跳到过的石子标号值和, ,
解题思路
一眼容斥。。这题的关键是怎么容斥。。
一般的容斥题是把需要容斥的东西放到一个vector里,然后两层循环第一层循环长度为,
为vector的长度。
第二层循环长度为,表示该对应的二进制在第位取还是不取。这样的容斥模型vector的长度会非常小,但这题vector的长度可能达到不能用这这种一般的容斥方法。。
对于每一个 都有一个
,而第i只青蛙所有能到达的格子就是k*
且小于等于m-1
而这里的一定是m的因子,所以对于一个很大的m我们可以先枚举所有m的所有因子,然后每个因子的对于答案的贡献就是一个等差数列:
的求和
而我们需要容斥的地方就是和
的公倍数,比如2和3 它们就会把6算了两次。
算法实现:对每一个m先预处理m的所有因子,排序。每次输入tem,数组a记录,对于每一个
标记其所有是其倍数的m的因子。 然后对m的因子进行容斥。
每个因子理应被访问fl[i]次,从小到大枚举,每次加上num[i]的贡献之后对所有num[j]%num[i]==0的j,容斥掉(减)当前num[i]多访问的次数。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <queue>
#define rep(i,a,b) for(repType i=(a);i<=(b);++i)
#define per(i,a,b) for(repType i=(a);i>=(b);--i)
#define fi first
#define se second
#define PB emplace_back
#define MP make_pair
#define int ll
using namespace std;
typedef long long ll;
typedef int repType;
#define maxn 1000005
int a[10005];
int num[maxn],fl[maxn];
int pri[maxn];
int cnt=0;
void init(int m)
{
cnt=0;
for(int i=1;i*i<=m;i++)
{
if(m%i==0)
{
pri[cnt++]=i;
pri[cnt++]=m/i;
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n,m;
int t;
//scanf("%lld",&t);
cin>>t;
for(int kase=1;kase<=t;kase++)
{
cout<<"Case #"<<kase<<": ";
cin>>n>>m;
memset(fl,0,sizeof(fl));
memset(pri,0,sizeof(pri));
memset(num,0,sizeof(num));
init(m);
sort(pri,pri+cnt);
ll tem;
for(int i=1;i<=n;i++)
{
cin>>tem;
a[i]=__gcd(tem,m);
for(int j=0;j<cnt-1;j++)
{
if(pri[j]%a[i]==0)
{
fl[j]=1;
//break;
}//所有ai的倍数都会被ai访问到
}
}
ll ans=0;
for(int i=0;i<cnt-1;i++)//从小到大枚举因子,不包括其本身
{
if(fl[i]!=num[i])//如果这个点flag=vis_time==1或者 flag==0&&vis_time==0,就不管
{
int tem=fl[i]-num[i];//每个因子理应被fl[i]次,num表示它当前被访问的次数
ans+=tem*pri[i]*(m/pri[i] * (m/pri[i]-1))/2;//每个因子的贡献
for(int j=i+1;j<cnt-1;j++)//当前因子会对之后因子产生的影响
{
if(pri[j]%pri[i]==0)
{
num[j]+=tem;//num[j]被多访问了tem次
}
}
}
}
cout<<ans<<endl;
}
}