这又是一篇训练系列的博文,主题是容斥原理。
题目:
UVA 10325 A - The Lottery
poj 3904 Sky Code
uvalive 7040 color
hdu 4059 The Boss on Mars
H - Visible Trees
UVA 10325 A - The Lottery
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1266
大意:给出一堆数字A,求在1-N内不被A中任意一个数字整除的个数。
分析:看到第二个样例,瞬间觉得不简单。假设这样一个例子:N=25, A=(6,8)
6:6、12、18、24
8:8、16、24
那么既能被6整除又能被8整除的数(最小数)难道是 6*8=48:0 吗?
显然这是不对的,既能被6整除又能被8整除的最小正整数该是24——最大公约数
多写写样例测试自己的程序很重要!!
poj 3904 Sky Code
http://poj.org/problem?id=3904
大意:求得在n个数字中最大公约数是1的四元组的个数
分析:正着求解是肯定不行的,逆向求解——容斥原理。用所有的素因子去求解所有的倍数,如果有四个数字的最大公约数是大于1的,那么一定存在素因子的组合是最大公约数的因子。所以对每一个数字素因子分解,记录每个因子,统计每个因子在这n个数中出现的次数和其素因子的组合个数。然后容斥,求出所有的非1四元组个数,最后总数减去它即可。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N=10010;
LL record[N],sum[N];
LL calc(LL n){
return 1LL*n*(n-1)*(n-2)*(n-3)/4/3/2;
}
LL fac[N],top;
void solve(LL n){
top=0;
for(LL i=2;i*i<=n;i++){
if(n%i==0){
fac[top++]=i;
while(n%i==0) n/=i;
}
}
if(n>1) fac[top++]=n;
}
int main(int argc, char *argv[]) {
LL n,w;
while(~scanf("%lld",&n)){
memset(record,0,sizeof(record));
memset(sum,0,sizeof(sum));
LL maxs=0;
for(LL i=0;i<n;i++){
scanf("%lld",&w);
solve(w);
for(LL j=1;j<(1<<top);j++){
LL g=0,s=1;
for(LL k=0;k<top;k++){
if(j&(1<<k)) {
g++;
s=s*fac[k];
}
}
sum[s]=g;
maxs=maxs>s?maxs:s;
record[s]++;
}
}
LL ans=calc(n),temp=0;
for(LL i=1;i<=maxs;i++){
if(sum[i]>0){
if(sum[i]&1) temp=temp+calc(record[i]);
else temp=temp-calc(record[i]);
}
}
ans=ans-temp;
printf("%lld\n",ans);
}
return 0;
}
uvalive 7040 color
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=5052
大意:N个物品排成一列,现在有m种颜色,从中选出k种颜色。要求相邻物品颜色不同地涂色。
分析:首先从m种颜色中选出k种颜色,
Ckm
两两不同的涂色:第一个物品涂色k种选择,,第二个物品涂色k-1种选择,第三个物品涂色k-1种选择……所以,得到
Ckmk(k−1)n−1
,但是这样的结果还包含了只用2种颜色涂色,只用3种颜色涂色,只用4种颜色涂色……用k-1种颜色涂色。所以,我们需要除去这些不相干的情况。所以在选出k种颜色,
Ckm
后,要再考虑
Ciki(i−1)n−1
的情况,同时
Ciki(i−1)n−1
又涉及到新的子集问题(我们不能直接
Ckmk(k−1)n−1−CkmCk−1k(k−1)(k−2)n−1
因为有选择的步骤
Cmn
),什么能解决这种树叶的投影类问题呢?容斥原理。
设
f(i)=Ciki(i−1)n−1
如k=5时,
C5m(f(1)−f(2)+f(3)−f(4)+f(5))
k=4时,
C4m(−f(1)+f(2)−f(3)+f(4))
即,括号里一定是
f(n)−f(n−1)+f(n−2)−f(n−3)⋯f(1)
注意:能预处理的预处理,不要造成不必要的多重循环,以免TLE。
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const LL N=1e6+10,mod=1e9+7;
LL C1[N],C2[N],inv[N],p[N];
LL power(LL a,LL p){
LL ans=1;
while(p){
if(p&1) ans=ans*a%mod;
a=a*a%mod;
p>>=1;
}
return ans;
}
void init(LL C[],LL n,LL m){
C[0]=1; C[1]=n;
for(int i=2;i<=m;i++){
//LL ni=power(i,mod-2);
C[i]=C[i-1]*(n-i+1)%mod*inv[i]%mod;
}
}
int main()
{
for(int i=1;i<N;i++) inv[i]=power(i,mod-2); // 预处理,乘法变加法 for less time
int t,ca=1;
cin>>t;
while(t--){
LL n,m,k;
scanf("%lld %lld %lld",&n,&m,&k);
init(C1,m,k);
init(C2,k,k);
LL ans=C2[k]*k%mod*power(k-1,n-1)%mod;
LL temp=0;
for(int i=1;i<k;i++){
p[i-1]=power(i-1,n-1);
}
for(int i=1;i<k;i++){
//if(i&1) temp=(temp+C2[i]*i%mod*power(i-1,n-1))%mod;
//else temp=(temp-C2[i]*i%mod*power(i-1,n-1)+mod)%mod;
if(i&1) temp=(temp+C2[i]*i%mod*p[i-1]%mod)%mod;
else temp=(temp-C2[i]*i%mod*p[i-1]%mod+mod)%mod;
}
if((k&1)==0)ans=(ans-temp+mod)%mod;
else ans=(ans+temp)%mod;
ans=ans*C1[k]%mod;
printf("Case #%d: %lld\n",ca++,ans);
}
return 0;
}
hdu 4059 The Boss on Mars
http://acm.hdu.edu.cn/showproblem.php?pid=4059
大意: 求出
∑x=1kx4
其中x和n互质
几个常用的求和公式:
他们均能由牛顿二项展开式+列项相消推出。
现在题目要求求出 ∑x4 其中x和n互质,那么可以先求出所以的 ∑x4 ,然后减去非互质的数。例如n=10. 那么,先求出 ∑x=110x4 ,然后减去2的倍数,5的倍数即可。但是这里有重复,于是容斥闪亮登场,2的倍数的4次方的和可以写成 24∑x=1kx4 。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const LL N=1e4+10,mod=1e9+7;
LL fac[N],top;
void solve(LL n){
top=0;
for(LL i=2;i*i<=n;i++){
if(n%i==0){
fac[top++]=i;
while(n%i==0) n/=i;
}
}
if(n>1) fac[top++]=n;
}
LL quick(LL a,LL p){
LL ans=1;
while(p){
if(p&1) ans=ans*a%mod;
a=a*a%mod;
p>>=1;
}
return ans;
}
LL ni;
LL sum(LL n){
return n*(n+1)%mod*(2*n%mod+1)%mod*(3*n*n%mod+3*n%mod-1)%mod*ni%mod;
}
int main()
{
LL t,n;
ni=quick(30,mod-2);
cin>>t;
while(t--){
scanf("%I64d",&n);
solve(n);
LL ans=sum(n);
LL t=0;
for(LL i=1;i<(1<<top);i++){
LL g=0,s=1;
for(LL j=0;j<top;j++){
if(i&(1<<j)){
g++;
s=s*fac[j]%mod;
}
} // s*fac[j]*fac[j]%mod*fac[j]%mod*fac[j]%mod*sum(n/fac[j])%mod
LL temp=s*s%mod*s%mod*s%mod*sum(n/s)%mod;
if(g&1) t=(t+temp)%mod;
else t=(t-temp+mod)%mod;
}
ans=(ans-t+mod)%mod;
printf("%I64d\n",ans);
}
return 0;
}
hdu 2841 - Visible Trees
http://acm.hdu.edu.cn/showproblem.php?pid=2841
大意:人站在(0,0), 矩阵左上角是(1,1),求解对于规模是m*n的矩阵人能看到的点数。
分析:如果一个点被另一个点挡住了,那么他们过(0,0)的斜率一定是一样的。即
yx
一定相等(最简形式),所以问题就变成了求解有多少个(x,y)互质对。
设m<0, 一部分的点数是
2∑i=2mϕ(i)+1
另一部分是当一条边大于另一条边时遍历每一个大于m的数,分解,容斥原理求得m内和它的互质数的个数。
#include <iostream>
#include <cstdio>
using namespace std;
const int N=1e5+10;
typedef long long LL;
LL phi[N];
void init(){
for(LL i=1;i<N;i++) phi[i]=i;
for(LL i=2;i<N;i++){
if(phi[i]==i) {
for(LL j=i;j<N;j+=i){
phi[j]=phi[j]-phi[j]/i;
}
}
}
}
LL fac[N/10],top;
void solve(LL n){
top=0;
for(LL i=2;i*i<=n;i++){
if(n%i==0){
fac[top++]=i;
while(n%i==0) n/=i;
}
}
if(n>1) fac[top++]=n;
}
int main()
{
init();
LL t,m,n;
cin>>t;
while(t--){
scanf("%lld%lld",&m,&n);
if(m>n){
m=m^n; n=m^n; m=m^n;
}
LL ans=0;
for(LL i=2;i<=m;i++){
ans=ans+phi[i];
}
ans=ans<<1;
ans++;
for(LL i=m+1;i<=n;i++){
solve(i);
LL temp=0;
for(LL j=1;j<(1<<top);j++){
LL s=1,g=0;
for(LL k=0;k<top;k++){
if(j&(1<<k)){
g++;
s=s*fac[k];
}
}
if(g&1) temp+=m/s;
else temp-=m/s;
}
ans=ans+m-temp;
}
printf("%lld\n",ans);
}
return 0;
}