[容斥][数论]JZOJ 5796 划分
分析
我们发现其实对于一个可以被识别的p(位置),一定有一对(i,j)
p%k[i]=0,p%k[j]=1
也就是:a*k[i]+b*k[j]=1 (mod p)这样一个同余方程
那么我们只要去重就行了,考虑容斥
那么两个互不相干的集合s1,s2的方程如下:
p%s1[0]=0 p%s1[1]=0 p%s1[2]=0 ...
p%s2[0]=1 p%s2[1]=1 p%s2[2]=1 ...
整个同余方程组可以整合为:a*lcm(|s1|)+b*lcm(|s2|)=1 (mod p),容斥系数为(-1)|s1|+|s2|
然后这个同余方程在题目范围内的解数为:(n div lcm(|s1|-a+lcm(|s2|))/|s2|(a为正整数且<n)
然后枚举时注意一下lcm(|s1|)和lcm(|s2|)都不能大于n,gcd(lcm(|s1|),lcm(|s2|))要等于1才有解
各种零注意判断一下
#pragma GCC optimize(3) #include <iostream> #include <cstdio> using namespace std; typedef long long ll; int m; ll n; ll a[12]; ll ans; inline ll Exgcd(ll a,ll b,ll &x,ll &y) { if (!b) { x=1;y=0; return a; } int ret=Exgcd(b,a%b,y,x); y-=a/b*x; return ret; } inline ll Lcm(ll a,ll b) { if (!a) return b; if (!b) return a; ll x,y; return a*b/Exgcd(a,b,x,y); } inline void Solve(int dep,ll s1,ll s2,int cnt) { if (s1>n||s2>n) return; if (dep==m) { if (s1==0||s2==0) return; ll x,y; if (Exgcd(s1,s2,x,y)!=1) return; x=(x%s2+s2)%s2; ans+=(cnt%2?-1ll:1ll)*(n/s1-x+s2)/s2; return; } Solve(dep+1,Lcm(s1,a[dep]),s2,cnt+1); Solve(dep+1,s1,Lcm(s2,a[dep]),cnt+1); Solve(dep+1,s1,s2,cnt); } int main() { freopen("sazetak.in","r",stdin); freopen("sazetak.out","w",stdout); scanf("%lld%d",&n,&m); for (register int i=0;i<m;i++) scanf("%lld",&a[i]); a[m++]=n; Solve(0,0,0,0); printf("%lld",ans); fclose(stdin);fclose(stdout); }
在日渐沉没的世界里,我发现了你。