不确定有没有(二)。
POJ2773 Happy 2006
方法一
二分答案 (mid),然后容斥出 ([1,mid]) 内与 (m) 互质的个数,(O(2^7log K))。
方法二
注意到答案就是缩系中的数加上 (km,forall kinmathbb{N})。用更损相减术证明平凡,(O(m))。
CF900D Unusual Sequences
注意到 (sum a_i) 必是 (x) 的倍数,所以如果 (x
mid y) 显然无解。否则另 (m=y/x),则题意转化为求 (gcdlimits_{i=1}^n a_i=1,sum a_i=m) 的序列个数。
先不考虑 (gcd a_i=1) 的条件,答案是 (sum_{i=1}^m{m-1choose i-1}=2^{m-1})(注意这里是有序数列,刚开始看错了懵了好久),记这个答案为 (g(m))。再记考虑了 (gcd a_i=1) 的限制后序列之和等于 (m) 的答案为 (f(m))。注意到 (forall dmid m),(f(d)) 对应的序列同时乘 (m/d),就对应了 (g(m)) 中满足条件的序列,即
莫比乌斯反演得
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,P=1e9+7;
int x,y,n,ans,pri[N],v[N];
int qpow(int p_)
{
int r_=1,b_=2;
for(;p_;p_>>=1,b_=1LL*b_*b_%P)
if(p_&1) r_=1LL*r_*b_%P;
return r_;
}
int mu(int x)
{
int res=1;
for(int i=1;i<=pri[0]&&1LL*pri[i]*pri[i]<=x;++i)
{
int cnt=0;
while(x%pri[i]==0) x/=pri[i],++cnt;
if(cnt>1) return 0;
else if(cnt==1) res*=-1;
}
if(x>1) res*=-1;
return res==-1?P-1:res;
}
int main()
{
scanf("%d%d",&x,&y),n=y/x;
if(y%x) return puts("0"),0;
for(int i=2;i<=N-5;++i)
{
if(!v[i]) pri[++pri[0]]=i;
for(int j=1;j<=pri[0]&&i*pri[j]<=N-5;++j)
{
v[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
for(int i=1;i*i<=n;++i) if(n%i==0)
{
(ans+=1LL*qpow(i-1)*mu(n/i)%P)%=P;
if(i*i!=n) (ans+=1LL*mu(i)*qpow(n/i-1)%P)%=P;
}
printf("%d",ans);
return 0;
}
[HAOI2008]圆上的整点
你想知道的都在这里。
按照视频中的想法,答案就是所有形如 (4k+1) 的质因子的次数加一之积乘 (4)。
#include <bits/stdc++.h>
using namespace std;
int r; long long ans=1;
int main()
{
scanf("%d",&r);
for(int i=2;i*i<=r;++i) if(r%i==0)
{
int p=0;
while(r%i==0) r/=i,++p;
if(i%4==1) ans*=p<<1|1;
}
if(r>1&&r%4==1) ans*=3;
printf("%lld",ans<<2);
return 0;
}
CF1229B Kamil and Making a Stream
有结论:所有不同的 (f(u,v)) 的值不超过 (O(log 10^{12})) 个,考虑一条路径上增加一个权值,如果此时 (gcd) 发生了变化,则必然是之前 (gcd) 的一个因子,这样最多不同的值只能有 (log) 个。我们可以每个点开个map
储存每个值的出现次数,向儿子转移即可,复杂度 (O(nlog^2 10^{12}))。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5,P=1e9+7;
int n; ll w[N],ans;
map<ll,int> p[N];
vector<int> G[N];
void dfs(int u,int fa)
{
++p[u][w[u]];
for(auto &it:p[u]) (ans+=it.first*it.second%P)%=P;
for(int v:G[u]) if(v!=fa)
{
for(auto &it:p[u])
p[v][__gcd(it.first,w[v])]+=it.second;
dfs(v,u);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",w+i);
for(int i=1,a,b;i<n;++i)
{
scanf("%d%d",&a,&b);
G[a].push_back(b),G[b].push_back(a);
}
dfs(1,0),printf("%lld",ans);
return 0;
}
CF1166E The LCMs Must be Large
有结论:能构造出符合限制的序列当且仅当所有集合两两有交。
反证法。设 (f(S)) 是集合 (S) 中所有位置数的最小公倍数。假设存在一个满足条件的序列,且有两个不交的集合 (A,B),那么有 (f(A)> f(U-A)ge f(B)>f(U-B)ge f(A)),上式中 (f(U-A)ge f(B)) 是因为如果 (S_1subset S_2),则显然 (f(S_1)le f(S_2))。上式推出了 (f(A)>f(A)) 的荒谬结果,很明显出现了矛盾,原命题得证。
考虑在满足条件的情况下如何构造这个序列,取 (m) 个互不相同的质数 (p_1,p_2,dots,p_m),令每个位置 (a_i=prod_{iin S_k}p_k)。注意到每个集合的 lcm 必是 (prod_{i=1}^mp_i),而这个集合的补集必没有 (p_i) 这个数,所以构造成立。
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,m;
bitset<N> s[N];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1,k,x;i<=m;++i)
{
scanf("%d",&k);
while(k--) scanf("%d",&x),s[i][x]=1;
}
for(int i=1;i<=m;++i)
for(int j=i+1;j<=m;++j)
if((s[i]&s[j]).none()) return puts("impossible"),0;
puts("possible");
return 0;
}
[NOI2010]能量采集
答案即是
考虑 (sum_{i=1}^nsum_{j=1}^mgcd(i,j)) 如何求,考虑容斥。
记 (g(x)) 等于公因数(不是最大)为 (x) 的点对个数,(f(x)) 则是最大公因数。
从后向前递推,(O(nlog n))。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m; long long f[N],ans;
int main()
{
scanf("%d%d",&n,&m);
if(n>m) swap(n,m);
for(int i=n;i;--i)
{
f[i]=1LL*(n/i)*(m/i);
for(int j=i*2;j<=n;j+=i) f[i]-=f[j];
ans+=(2*i-1)*f[i];
}
printf("%lld",ans);
return 0;
}
[CQOI2015]选数
和上一道题一样,考虑先 (Lgets L/K,Rgets R/K),那么现在变成在 ([L,R]) 中选择 (n) 个最大公约数为 (1) 的方法。设 (f(i)) 为选出的最大公约数为 (i) 且不全相同的方案数,则有
其中 (x) 是区间 ([L,R]) 中 (i) 的倍数个数。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,P=1e9+7;
int f[N],n,k,l,r;
int qpow(int b_,int p_)
{
int r_=1;
for(;p_;p_>>=1,b_=1LL*b_*b_%P)
if(p_&1) r_=1LL*r_*b_%P;
return r_;
}
int main()
{
scanf("%d%d%d%d",&n,&k,&l,&r),l=l/k+(l%k>0),r/=k;
if(l>r) return puts("0"),0;
for(int i=r-l;i;--i)
{
int L=l/i+(l%i>0),R=r/i;
if(L>R) continue;
f[i]=(qpow(R-L+1,n)-(R-L+1)+P)%P;
for(int j=i*2;j<=r-l;j+=i) f[i]=(f[i]-f[j]+P)%P;
}
printf("%d",(f[1]+(l==1))%P);
return 0;
}
[SCOI2010]幸运数字
先预处理出幸运数字,然后爆搜容斥,当然不优化显然会 T。
考虑降序排序,并且提前预处理出互为因子的数,只留一种。容斥时如果超过边界及时剪纸(注意中间过程有可能爆long long
)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4030;
ll l,r,a[N],num[N],ans;
int n,tot,vis[N];
void getnum(ll now)
{
if(now>r) return;
if(now) a[++n]=now;
getnum(now*10+6),getnum(now*10+8);
}
void dfs(int id,int sgn,ll val)
{
if(val>r) return;
if(id>tot)
{
if(!sgn) return;
ans+=(r/val-l/val-(l%val>0)+1)*((sgn&1)?1:-1);
return;
}
dfs(id+1,sgn,val); val=val/__gcd(val,num[id]);
if(1.0*val*num[id]<=r) dfs(id+1,sgn+1,val*num[id]);
}
int main()
{
scanf("%lld%lld",&l,&r);
getnum(0),sort(a+1,a+n+1);
for(int i=1;i<=n;++i)
{
if(!vis[i]) num[++tot]=a[i];
for(int j=i+1;j<=n;++j)
if(a[j]%a[i]==0) vis[j]=1;
}
reverse(num+1,num+tot+1);
dfs(1,0,1),printf("%lld",ans);
return 0;
}
SGU499 Greatest Greatest Common Divisor
sb 题,塞桶里,然后倒着枚举值域就行了。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,a[N];
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(a,0,sizeof(a));
for(int i=1,x;i<=n;++i)
scanf("%d",&x),++a[x];
for(int i=N-5;i;--i)
{
int cnt=0;
if(a[i]) ++cnt;
for(int j=2*i;j<=N-5;j+=i) cnt+=a[j];
if(cnt>1) {printf("%d
",i);break;}
}
}
return 0;
}
HDOJ4135 Co-prime
身败名裂.jpg
又是一道裸容斥,不放代码了。
CF1176D Recover it!
把序列按照质数与合数分开,合数降序排序,质数升序排序。遍历合数时,如果出现次数为零则为答案,否则将出现次数减一,将最大真因子加一,质数同理。容易发现这样子一定是对的。
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5,M=2750131+5;
int n,pri[N],p[N*2],d[N*2],t1,t2,mind[M];
bool v[M]; map<int,int> mp;
int main()
{
for(int i=2;i<=M-5;++i)
{
if(!v[i]) pri[++pri[0]]=i;
for(int j=1;j<=pri[0]&&i*pri[j]<=M-5;++j)
{
v[i*pri[j]]=1,mind[i*pri[j]]=pri[j];
if(i%pri[j]==0) break;
}
}
scanf("%d",&n);
for(int i=1,x;i<=n*2;++i)
{
scanf("%d",&x);
if(v[x]) d[++t2]=x;
else p[++t1]=x;
}
sort(p+1,p+t1+1),sort(d+1,d+t2+1,greater<int>());
for(int i=1;i<=t2;++i)
{
if(mp[d[i]]) {--mp[d[i]];continue;}
printf("%d ",d[i]);
++mp[d[i]/mind[d[i]]];
}
for(int i=1;i<=t1;++i)
{
if(mp[p[i]]) {--mp[p[i]];continue;}
printf("%d ",p[i]),++mp[pri[p[i]]];
}
return 0;
}
CF1174E Ehab and the Expected GCD Problem
很牛逼的一道题。参考题解
我们想让不同 (gcd) 个数尽量多,那么首先要让第一个数的 (gcd) 尽量大,每次要减的尽量少。那么第一位只能是 (2^x3^y) 形式((yin{0,1}))。
记 (f_{i,x,y}) 表示第 (i) 位形如 (2^x3^y) 的序列个数,转移详见代码。
#include <bits/stdc++.h>
#define c(x) (n/(x))
using namespace std;
const int N=1e6+5,P=1e9+7;
int n,k,f[N][21][2];
int main()
{
scanf("%d",&n),f[1][k=log2(n)][0]=1;
if((1<<k-1)*3<=n) f[1][k-1][1]=1;
for(int i=2;i<=n;++i)
for(int j=0;j<=k;++j)
f[i][j][0]=(1LL*f[i-1][j][0]*(c(1<<j)-i+1)+
1LL*f[i-1][j+1][0]*(c(1<<j)-c(1<<j+1))+
1LL*f[i-1][j][1]*(c(1<<j)-c((1<<j)*3)))%P,
f[i][j][1]=(1LL*f[i-1][j][1]*(c((1<<j)*3)-i+1)+
1LL*f[i-1][j+1][1]*(c((1<<j)*3)-c((1<<j+1)*3)))%P;
printf("%d",f[n][0][0]);
return 0;
}
HDOJ5514 Frogs
一个青蛙能跳到的点为 (gcd(a_i,m)),那么我们考虑只在 (m) 的因子处统计这些值,将因子提出来后答案就是与这个值互素的值之和,也就是 (sumlimits_{dmid m}varphi(m/d)m/2)
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int n,m,a[N],fl,fac[N],tot;
long long ans;
int phi(int x)
{
int res=x;
for(int i=2;i*i<=x;++i) if(x%i==0)
{
while(x%i==0) x/=i;
res=res/i*(i-1);
}
if(x>1) res=res/x*(x-1);
return res;
}
int main()
{
int Case; scanf("%d",&Case);
for(int T=1;T<=Case;++T)
{
scanf("%d%d",&n,&m),fl=ans=tot=0;
for(int i=1;i<=n;++i)
scanf("%d",a+i),a[i]=__gcd(m,a[i]),fl|=(a[i]==1);
printf("Case #%d: ",T);
if(fl) {printf("%lld
",1LL*(m-1)*m/2); continue;}
for(int i=2;i*i<=m;++i) if(m%i==0)
{
fac[++tot]=i;
if(i*i!=m) fac[++tot]=m/i;
}
for(int i=1;i<=tot;++i)
for(int j=1;j<=n;++j) if(fac[i]%a[j]==0)
{ans+=1LL*phi(m/fac[i])*m/2; break;}
printf("%lld
",ans);
}
return 0;
}