容斥专题三

本文总结了容斥原理在算法竞赛中的应用,通过多个题目详细解析了如何利用容斥原理解决组合计数、棋盘放置、圆环相交、石子跳跃等问题。文章介绍了如何优化复杂度,以及在不同场景下的解题思路,如利用高精度、同余方程、DFS搜索等方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

抱歉,抱歉,前两周忙着打网络赛,结果容斥专题断了整整两周。最后一个专题,总结性专题,现在继续。



第一题SGU-476

题意:3n个人,组成n个三人组,而且必须满足k组条件,每组条件表示a编号为a,b,c的三人不可以一个队。

分析:是容斥原理,用所有情况减去不能出现的情况,就是答案。对于不能出现的情况,对于条件的组数进行容斥,如果多组情况中有相同的成员编号那么就是不可能出现的,为0。这样的话,代码还是很好写的,而且用dfs的写法,还可以进行剪枝。

观察数据范围,发现答案很大,必须使用高精度。使用高精度打表的时候发现复杂度很高,预处理处理不出来,一个是高精度的模版不行(果断换掉),还有就是简化打表公式。对于没有条件的3n个人,方案数是 (3n)!6nn! ,知道通项,很方便的得到递推式 n0=1,ni=ni1(3n1)(3n2)2 ,这样打表很显然快很多。

但是这样的话,对于极限情况还是很慢,因为进行了多次大数相加(大约为1000的复杂度),递归深度是20,那么总的复杂度就是 1000220=109 ,极限情况是会TLE的。

之后,我考虑对于每一组具有相同有效条件数的搜索,它的方案数和符号都是一样的,那么我可以先将他们累积起来,再直接进行一次大数乘法和一次大数加法。这样的话,将时间复杂度优化到了10^6(常数可能有点大),就可以过了。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          1000000+10
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------
int compare(string str1,string str2)  
{  
    if(str1.length()>str2.length()) return 1;  
    else if(str1.length()<str2.length())  return -1;  
    else return str1.compare(str2);  
}  
string add(string str1,string str2)
{  
    string str;  

    int len1=str1.length();  
    int len2=str2.length();  
    if(len1<len2)  
    {  
        for(int i=1;i<=len2-len1;i++)  
           str1="0"+str1;  
    }  
    else  
    {  
        for(int i=1;i<=len1-len2;i++)  
           str2="0"+str2;  
    }  
    len1=str1.length();  
    int cf=0;  
    int temp;  
    for(int i=len1-1;i>=0;i--)  
    {  
        temp=str1[i]-'0'+str2[i]-'0'+cf;  
        cf=temp/10;  
        temp%=10;  
        str=char(temp+'0')+str;  
    }  
    if(cf!=0)  str=char(cf+'0')+str;  
    return str;  
}  
string sub(string str1,string str2)
{  
    string str;  
    int tmp=str1.length()-str2.length();  
    int cf=0;  
    for(int i=str2.length()-1;i>=0;i--)  
    {  
        if(str1[tmp+i]<str2[i]+cf)  
        {  
            str=char(str1[tmp+i]-str2[i]-cf+'0'+10)+str;  
            cf=1;  
        }  
        else  
        {  
            str=char(str1[tmp+i]-str2[i]-cf+'0')+str;  
            cf=0;  
        }  
    }  
    for(int i=tmp-1;i>=0;i--)  
    {  
        if(str1[i]-cf>='0')  
        {  
            str=char(str1[i]-cf)+str;  
            cf=0;  
        }  
        else  
        {  
            str=char(str1[i]-cf+10)+str;  
            cf=1;  
        }  
    }  
    str.erase(0,str.find_first_not_of('0')); 
    return str;  
}  
string mul(string str1,string str2)  
{  
    string str;  
    int len1=str1.length();  
    int len2=str2.length();  
    string tempstr;  
    for(int i=len2-1;i>=0;i--)  
    {  
        tempstr="";  
        int temp=str2[i]-'0';  
        int t=0;  
        int cf=0;  
        if(temp!=0)  
        {  
            for(int j=1;j<=len2-1-i;j++)  
              tempstr+="0";  
            for(int j=len1-1;j>=0;j--)  
            {  
                t=(temp*(str1[j]-'0')+cf)%10;  
                cf=(temp*(str1[j]-'0')+cf)/10;  
                tempstr=char(t+'0')+tempstr;  
            }  
            if(cf!=0) tempstr=char(cf+'0')+tempstr;  
        }  
        str=add(str,tempstr);  
    }  
    str.erase(0,str.find_first_not_of('0'));  
    return str;  
}  

void div(string str1,string str2,string &quotient,string &residue)  
{  
    quotient=residue="";
    if(str2=="0")
    {  
        quotient=residue="ERROR";  
        return;  
    }  
    if(str1=="0")
    {  
        quotient=residue="0";  
        return;  
    }  
    int res=compare(str1,str2);  
    if(res<0)  
    {  
        quotient="0";  
        residue=str1;  
        return;  
    }  
    else if(res==0)  
    {  
        quotient="1";  
        residue="0";  
        return;  
    }  
    else  
    {  
        int len1=str1.length();  
        int len2=str2.length();  
        string tempstr;  
        tempstr.append(str1,0,len2-1);  
        for(int i=len2-1;i<len1;i++)  
        {  
            tempstr=tempstr+str1[i];  
            tempstr.erase(0,tempstr.find_first_not_of('0'));  
            if(tempstr.empty())  
              tempstr="0";  
            for(char ch='9';ch>='0';ch--)
            {  
                string str,tmp;  
                str=str+ch;  
                tmp=mul(str2,str);  
                if(compare(tmp,tempstr)<=0) 
                {  
                    quotient=quotient+ch;  
                    tempstr=sub(tempstr,tmp);  
                    break;  
                }  
            }  
        }  
        residue=tempstr;  
    }  
    quotient.erase(0,quotient.find_first_not_of('0'));  
    if(quotient.empty()) quotient="0";  
}  
string dat[1000+10];
string ans,ant;
int tri[20+10][3];
int nun[20+10];
int vis[3000+10];
int n,k;
string Multiply(string s,int x)
{  
    reverse(s.begin(),s.end());  
    int cmp=0;  
    for(int i=0;i<s.size();i++)  
    {  
        cmp=(s[i]-'0')*x+cmp;  
        s[i]=(cmp%10+'0');  
        cmp/=10;  
    }  
    while(cmp)  
    {  
        s+=(cmp%10+'0');  
        cmp/=10;  
    }  
    reverse(s.begin(),s.end());  
    s.erase(0,s.find_first_not_of('0'));
    return s;  
}  
void init(){
    dat[0]="1";
    for(int i=1;i<=1000;i++)
        dat[i]=Multiply(dat[i-1],(3*i-2)*(3*i-1)/2);
}
void dfs(int len,int num){
    if(len==k){
        if(num)nun[num]++;
        return ;
    }
    dfs(len+1,num);
    for(int i=0;i<3;i++)
        if(vis[tri[len][i]])return ;
    for(int i=0;i<3;i++)
        vis[tri[len][i]]=1;
    dfs(len+1,num+1);
    for(int i=0;i<3;i++)
        vis[tri[len][i]]=0;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    init();
    while(~scanf("%d %d",&n,&k)){
        ans=dat[n];
        ant="0";
        for(int i=0;i<k;i++)
            for(int j=0;j<3;j++)
                scanf("%d",&tri[i][j]);
        clr(vis,0);
        clr(nun,0);
        dfs(0,0);
        for(int i=1;i<=k;i++)
            if(nun[i]){
                if(i&1)ant=add(ant,Multiply(dat[n-i],nun[i]));
                else ans=add(ans,Multiply(dat[n-i],nun[i]));
            }
        string anw=sub(ans,ant);
        if(anw.size())cout<<sub(ans,ant)<<endl;
        else puts("0");
    }
    return 0;
}


第二题uva-10325

分析:容斥的简单题目,看前面的专题。某个区间中不能被集合连任何一个数整除的数的个数?

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          300+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

int dat[20];
int n,m;
ll lcm(ll a,ll b){
    return a/__gcd(a,b)*b;
}
ll solve(){
    ll ans=0;
    for(int i=1;i<(1<<m);i++){
        ll t=1,num=0;
        for(int j=0;j<m;j++)
            if(i&(1<<j)){
                t=lcm(t,dat[j]);
                num++;
            }
        if(num&1)ans+=n/t;
        else ans-=n/t;
    }
    return n-ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    while(~scanf("%d %d",&n,&m)){
        for(int i=0;i<m;i++)
            scanf("%d",&dat[i]);
        printf("%lld\n",solve());
    }
    return 0;
}



第三题uva-11806

题意:将 k 个棋子放在 n×m 的棋盘上。我们将连续的紧贴棋盘边界的放置棋子位置称为边缘。问上下左右四个边缘上都至少放置一个棋子的棋子放置方案共有多少种。

分析:正难则反嘛,考虑将所有情况减去上下左右四个边不都放置一个棋子的情况。首先,看所有情况,很容易考虑,即将所有 nm 个格子取出 k 个,是C(knm)。然后将上下左右四个边分别看作是一个单独的分类标记,按照容斥原理的分类就是:假如上边没有放置棋子,那么就是 C(k(n1)m) ;假如上边和右边没有放置棋子,那么就是 C(k(n1)(m1)) ;其他的情况以此类推。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          300+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e6+7;//1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

ll dat[444][444];
void init(){
    for(int i=0;i<=400;i++){
        dat[i][0]=dat[i][i]=1;
        for(int j=1;j<i;j++)
            dat[i][j]=(dat[i-1][j]+dat[i-1][j-1])%mod;
    }
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    init();
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        int n,m,k;
        scanf("%d %d %d",&n,&m,&k);
        printf("Case %d: ",cas);
        if(k>n*m){
            puts("0");
            continue;
        }
        ll ans=dat[n*m][k];
        for(int i=1;i<(1<<4);i++){
            ll a=n,b=m,num=0;
            for(int j=0;j<4;j++)
                if(i&(1<<j)){
                    num++;
                    if(j==0||j==1)a--;
                    else b--;
                }
            if(k>a*b)continue;
            if(num&1)ans=(ans-dat[a*b][k]+mod)%mod;
            else ans=(ans+dat[a*b][k])%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}



第四题hdu-5120

题意:求平面直角坐标系上的两个圆环的相交部分的面积。

分析:虽然感觉和容斥原理的关系不是很大,但是还是拿过来凑数了。做这道题目的时候,我画了很多的图,用阴影表示相交的部分,然后就能猜出规律了。要求的面积是两个大圆的相交部分,减去两倍的大圆和小圆的相交部分,加上两个小圆的相交部分。一开始我以为是减去两个小圆的相交部分,但是画了大圆和小圆同时相交的情况就可以看出其实是减去小圆相交的部分。

虽然思路很快想到了,但是以前基本没有敲过几何题,结果余弦定理忘了,海伦公式忘了,两圆之间的关系忘了,两圆相交的公式忘了。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          300+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

double x1,y_1;
double x2,y2;
double r,R;
double len;
double percent(double a,double b,double c){
    return acos((a*a+b*b-c*c)/2/a/b)/2/pi;
}
double dis(double a1,double b1,double a2,double b2){
    a1-=a2;b1-=b2;
    return sqrt(a1*a1+b1*b1);
}
double cal(double a,double b,double c){
    double p=(a+b+c)/2;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}
double solve(double x,double y){
    if(len>=x+y)return 0;
    if(len<=y-x)return pi*x*x;
    double ll=pi*y*y*percent(len,y,x);
    //cout<<ll<<endl;
    double rr=pi*x*x*percent(len,x,y);
    //cout<<rr<<endl;
    double tri=cal(x,y,len);
    return 2*(ll+rr-tri);
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%lf %lf",&r,&R);
        scanf("%lf %lf %lf %lf",&x1,&y_1,&x2,&y2);
        len = dis(x1,y_1,x2,y2);
        double ans;
        if(len==0)ans=pi*(R*R-r*r);
        else ans=solve(R,R)-2*solve(r,R)+solve(r,r);
        printf("Case #%d: %.6f\n",cas,ans);
    }
    return 0;
}



第五题hdu-5514

题意:有 m 个石子围成一圈,编号为 0 ~m1 。又有 n 只青蛙,每只青蛙的跳跃距离为 ai ,所有青蛙从 0 号石子起跳,路过的石子都打上标记(每个青蛙的标记无区别),问最后被打上标记的石子的标号和是多少。

分析:m个石子排成一个环,青蛙在这个环之间不断跳跃,但是它总会跳到它以前跳到过的石子上,那么就形成了循环。多次考虑发现 gcd(ai,m) 是永远不可能到达的距离,那么所有 gcd(ai,m) 的倍数都会被青蛙跳上(其他都必定不能被跳上),简化了数据。考虑每个与m取gcd的 ai ,0的话忽略不计,1的话那么直接所有数都会被走遍,可以简化计算。而且可以把所有数据排一下序,然后去一下重,可以删掉很多重复数据。这样剩下的数据就是不相同的,还都是m的因数。对于m的因数进行搜索(容斥)的话,很多情况是重复的。这时候dfs搜索+剪枝就是最好的解法,对于当前的mult,可以被一个 ai 整除的话,那么接下去搜索是无意义的,因为这种情况和单独从 ai 开始计算是一样的,没有必要计算,这种剪枝可以剪掉很多。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          10000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

int dat[maxn];
int n,m;
ll ans;
ll cal(ll n,ll k){
    ll num=n/k;
    return (num+1)*num/2*k;
}
ll lcm(ll a,ll b){
    return a/__gcd(a,b)*b;
}
void dfs(int cur,ll mult,int num){
    if(cur==n){
        if(num){
            //cout<<cal(m-1,mult)<<" "<<mult<<endl;
            if(num&1)ans+=cal(m-1,mult);
            else ans-=cal(m-1,mult);
        }
        return ;
    }
    if(mult%dat[cur]==0)return;
    dfs(cur+1,mult,num);
    dfs(cur+1,lcm(mult,dat[cur]),num+1);
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%d %d",&n,&m);
        for(int i=0;i<n;i++){
            scanf("%d",&dat[i]);
            dat[i]=__gcd(dat[i],m);
            if(!dat[i]){
                n--;
                i--;
            }
        }
        printf("Case #%d: ",cas);
        sort(dat,dat+n);
        if(dat[0]==1){
            printf("%lld\n",1ll*m*(m-1)/2);
            continue;
        }
        n=unique(dat,dat+n)-dat;
        ans=0;
        dfs(0,1,0);
        printf("%lld\n",ans);
    }
    return 0;
}



第六题 hdu-5768

题意:给定一个区间 [x,y] 和n个数对 (pi,ai) ,问在区间中的能被7整除的,并且对所有i,被 pi 除不余 ai 的数共有多少个。

分析:首先幸运数需要能够被7整除,在所有能被7整除的数中进行讨论。不妨将所有区间中能被7整除的数的个数求出来,然后减去既能被7整除还能满足被 pi 除余 ai 的数的个数,就是结果。对于n组需要满足的条件,不妨以他们进行容斥。每一个条件实际上就是一个同余方程,利用中国剩余定理将若干同余方程整理成一个,然后在 [x,y] 中求出满足方程的数的个数,就是反面要求的个数。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          10000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

ll p[20];
ll a[20];
ll x,y;
ll aa[maxn];
ll bb[maxn];
ll cal(ll n,ll k,ll a){
    if(n<a)return 0;
    else return 1+(n-a)/k;
}
ll exgcd(ll a,ll b,ll& x,ll& y){
    if(b==0){
        x=1;
        y=0;
        return a;
    }
    ll t=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return t;
}
ll inv(ll a,ll n){
    ll x,y;
    ll d=exgcd(a,n,x,y);
    if(d!=1)return -1;
    return (x%n+n)%n;
}
ll china(ll a[],ll b[],ll n){
    ll M=1,ans=0;
    for(int i=0;i<n;i++)
        M*=aa[i];
    for(int i=0;i<n;i++)
        ans=(ans+M/aa[i]*bb[i]*inv(M/aa[i],aa[i]))%M;
    return cal(y,M,ans)-cal(x-1,M,ans);
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        int n;
        scanf("%d %lld %lld",&n,&x,&y);
        for(int i=0;i<n;i++)
            scanf("%lld %lld",&p[i],&a[i]);
        ll ans=cal(y,7,0)-cal(x-1,7,0);
        //cout<<ans<<endl;
        for(int i=1;i<(1<<n);i++){
            int num=0;
            for(int j=0;j<n;j++)
                if(i&(1<<j)){
                    aa[num]=p[j];
                    bb[num]=a[j];
                    num++;
                }
            aa[num]=7;
            bb[num]=0;
            num++;
            if(num&1)ans+=china(aa,bb,num);
            else ans-=china(aa,bb,num);
        }
        printf("Case #%d: %lld\n",cas,ans);
    }
    return 0;
}



第七题 hdu-4451

题意:有n件衣服,m条裤子,k双鞋子。要求有p条,每一条要求规定X号衣服不能和Y号裤子同时穿;或者规定Z号裤子不能和W号鞋子同时穿。问有多少种穿法?

分析:比较简单的题目,将所有的情况(n*m*k),减去不能穿的情况。不能穿的情况需要好好思考,不妨累积所有衣服与裤子不能穿的情况数是XX,所有裤子与鞋子不能穿的情况数是YY,那么带有不能穿的衣服搭配就是XX*k+YY*n。但是其中不能搭配的有的多计算了,按照容斥需要减去。这里必须对于每一个衣服的类型进行累积,看每一种禁止搭配对于相同的衣服类型有多少个,相乘加上答案,得到最终结果。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          10000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

char str1[20],str2[20];
int a,b;
int dat1[1111],dat2[1111];
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n,m,k;
    while(scanf("%d %d %d",&n,&m,&k),n||m||k){
        int p;
        scanf("%d",&p);
        int xx=0,yy=0;
        clr(dat1,0);
        clr(dat2,0);
        while(p--){
            scanf("%s %d %s %d",str1,&a,str2,&b);
            if(str1[0]=='c'){
                xx++;
                dat1[b]++;
            }
            else {
                dat2[a]++;
                yy++;
            }
        }
        int ans=n*m*k-xx*k-yy*n;
        for(int i=1;i<=1000;i++)
            if(dat1[i]&&dat2[i])
                ans+=dat1[i]*dat2[i];
        printf("%d\n",ans);
    }
    return 0;
}



第八题 hdu-5468

题意:n个点、n-1条边的树,节点用1~n来编号(1号为根结点),给出每个节点的值。对于每一个节点,询问与之互素的孙子节点的个数。

分析:这道题很难,看题解做的。考虑dfs,开始搜索这个节点的子节点和已经搜索完这个节点的子节点的状态是完全不一样的。不妨记录子节点的所有由不同的质因数组成的因子,考虑在记录里面和当前节点值互素的数的个数,就是以这个节点值的质因子来容斥。

先将所有数的质因数处理出来。从1开始dfs搜索,在搜索这个节点之前,找出记录里面与此时节点值不互素的数的个数LL。然后向下搜索,等搜索结束了(记录里面已经有了这个节点的所有子节点的记录了),再次找出记录里面与这个数不互素的数的个数RR,然后在子节点里面与父节点互素的数就是tot-(LL-RR),tot是父节点的所有子节点的个数。那么问题是怎么求此时在记录里面与当前节点值不互素的数的个数,当然线性查找的话肯定会超时的。

这种套路在以前专题上有写过,将素因子预处理出来,然后找出与某个值不互素的情况。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          100000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-10;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

vector<int>p[maxn];
vector<int>dat[maxn];
int vis[maxn];
int anw[maxn];
int val[maxn];
void init(){
    for(int i=2;i<=100000;i++)
        if(dat[i].empty())
            for(int j=i;j<=100000;j+=i)
                dat[j].PB(i);
}
int cal(int x,int t){
    int ans=0;
    for(int i=1;i<(1<<dat[x].size());i++){
        int mult=1,num=0;
        for(int j=0;j<dat[x].size();j++)
            if(i&(1<<j)){
                mult*=dat[x][j];
                num++;
            }
        ans=max(vis[mult],ans);
        if(t)vis[mult]++;
    }
    return ans;
}
int dfs(int u,int fa){
    int L=cal(val[u],0);
    int tot=0;
    for(int i=0;i<p[u].size();i++){
        int v=p[u][i];
        if(v==fa)continue;
        tot+=dfs(v,u);
    }
    int R=cal(val[u],1);
    anw[u]=tot-(R-L);
    if(val[u]==1)anw[u]++;
    return tot+1;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    init();
    int n,cas=1;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            p[i].clear();
            vis[i]=0;
        }
        for(int i=1;i<n;i++){
            int u,v;
            scanf("%d %d",&u,&v);
            p[u].PB(v);
            p[v].PB(u);
        }
        for(int i=1;i<=n;i++)
            scanf("%d",&val[i]);
        dfs(1,-1);
        printf("Case #%d:",cas++);
        for(int i=1;i<=n;i++)
            printf(" %d",anw[i]);
        puts("");
    }
    return 0;
}



第九题 hdu-3388

题意:求出与n和m都互素的第K大的正整数。

分析:也没有什么好想的,就是按照poj2773的思路。这里就是将与n互素的改成了与n和m都互素的,那么我首先将题意往poj2773上靠,与n和m同时互素,等价于与n和m的最小公倍数互素,那么就是求出最小公倍数转化题意。但是n和m都是1e9的级别,最小公倍数更大,这样做在求质因数上就不好求了。所以,更好的思路就是,反正是求质因数,那么不如直接分解n和m,然后将所有质因数去重即可。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          100000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-14;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

int tot;
bool isprime[maxn];
int prime[maxn];
void init(int n){
    for(int i=2;i<=n;i++){
        if(!isprime[i])
            prime[tot++]=i;
        for(int j=0;1ll*prime[j]*i<=n;j++){
            isprime[prime[j]*i]=true;
            if(i%prime[j]==0)break;
        }
    }
}
int cnt;
ll p[maxn];
void cal(ll n){
    for(int i=0;i<tot&&prime[i]<=n;i++)
        if(n%prime[i]==0){
            p[cnt++]=prime[i];
            while(n%prime[i]==0)
                n/=prime[i];
        }
    if(n>1)p[cnt++]=n;
}
ll check(ll n){
    ll ans=0;
    for(int i=1;i<(1<<cnt);i++){
        ll mult=1,num=0;
        for(int j=0;j<cnt;j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        if(num&1)ans+=n/mult;
        else ans-=n/mult;
    }
    return  n-ans;
}
int main(){
    int T;
    init(100000);
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        ll n,m,k;
        scanf("%lld %lld %lld",&n,&m,&k);
        cnt=0;
        cal(n);cal(m);
        sort(p,p+cnt);
        cnt=unique(p,p+cnt)-p;
        ll l=1,r=(ll)1e18;
        while(l<=r){
            ll mid=(l+r)>>1;
            if(check(mid)>=k)r=mid-1;
            else l=mid+1;;
        }
        printf("Case %d: %lld\n",cas,l);
    }
    return 0;
}


第十题 hdu-5212

题意:给一个数组 an ,求 1i,jngcd(ai,aj)(gcd(ai,aj)1)

分析:首先分析每个数对最终答案的贡献。

那么对于某个数x,我们需要知道有多少组不同的序列(x,y)是gcd(x,y)等于x。如果我们知道了的话,那么枚举每一个x和对应x的 fx (不妨设为gcd为x的组数),直接累加答案就行了。考虑gcd为x,那么这两个数一定是x的倍数,在这 an 数组中易知是x的倍数的数有多少个,不妨设为k个。但是这k个数中,并不是每两个gcd一下正好等于k,有可能等于2k、3k、4k、。。。,那么考虑将他们减掉。最后得到结论: fx=kkf2kf3k...... ,这样我们倒着维护一个F数组就可以了。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          10000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-14;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

int cnt[maxn];
int F[maxn];
int m=10007;
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n;
    while(~scanf("%d",&n)){
        clr(cnt,0);
        for(int i=0;i<n;i++){
            int a;
            scanf("%d",&a);
            for(int j=1;j*j<=a;j++)
                if(a%j==0){
                    cnt[j]++;
                    if(j*j!=a)
                        cnt[a/j]++;
                }
        }
        ll ans=0;
        for(int i=10000;i>=2;i--){
            F[i]=cnt[i]*cnt[i]%m;
            for(int j=i+i;j<=10000;j+=i)
                F[i]=(F[i]-F[j]+m)%m;
            ans=(ans+i*(i-1)%m*F[i])%m;
        }
        cout<<ans<<endl;
    }
    return 0;
}


第十一题 hrbust-1191

分析:比较简单的一道题,用到了错排公式。如果你不记得错排公式了,可以用容斥原理去推一下,很简单的。最后答案是 C(nmn)(m!+mi=1(1)im!i!)

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          10000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-14;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

ll dat[22][22];
ll fac[22];
void init(){
    for(int i=0;i<=20;i++){
        if(i)fac[i]=fac[i-1]*i;
        else fac[i]=1;
        dat[i][0]=dat[i][i]=1;
        for(int j=1;j<i;j++)
            dat[i][j]=dat[i-1][j]+dat[i-1][j-1];
    }
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int t;
    init();
    scanf("%d",&t);
    while(t--){
        int n,m;
        scanf("%d %d",&n,&m);
        ll ans=dat[n][n-m];
        ll tmp=fac[m],k=fac[m];
        for(int i=1;i<=m;i++){
            k/=i;
            if(i&1)tmp-=k;
            else tmp+=k;
        }
        cout<<ans*tmp<<endl;;
    }
    return 0;
}


第十二题 Gym 100548F

题意:给你n朵花,m种颜料,要求给所有花染色,且相邻的花不能用同样的颜色染色,求出最后恰好用了k种颜料的方案数

分析:首先要在m种颜料中选出k种颜料,然后利用容斥原理将恰好使用k种颜料的方案数算出来。按照使用颜料数来容斥,考虑要使用的颜料数对于i,方案数是 C(ik)i(i1)n1 。那么最后结论就是 C(km)k2i=0C(kik)(ki)(ki1)n1(1)i ,小数据特判。

注意:k的数据大部分很小,那么写法上适当的暴力一点是没有问题。

#include <map>
#include <set>
#include <ctime>
#include <stack>
#include <cmath>
#include <queue>
#include <bitset>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <fstream>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <iostream>
#include <algorithm>
#pragma comment(linker, "/STACK:1024000000,1024000000")

using namespace std;
#define   maxn          1000000+10
#define   SIZE          10000+5
#define   lson          l,m,rt<<1
#define   rson          m+1,r,rt<<1|1
#define   clr(x,y)      memset(x,y,sizeof(x))
#define   rep(i,n)      for(int i=0;i<(n);i++)
#define   repf(i,a,b)   for(int i=(a);i<=(b);i++)
#define   pii           pair<int,int>
#define   mp            make_pair
#define   FI            first
#define   SE            second
#define   IT            iterator
#define   PB            push_back
#define   Times         10

typedef   long long     ll;
typedef   unsigned long long ull;
typedef   long double   ld;

const double eps   = 1e-14;
const double  pi   = acos(-1.0);
const  ll    mod   = 1e9+7;
const  int   inf   = 0x3f3f3f3f;
const  ll    INF   = (ll)1e18+300;
const double delta = 0.98;

inline void RI(int& x)
{
    x=0;
    char c=getchar();
    while(!((c>='0'&&c<='9')||c=='-'))c=getchar();
    bool flag=1;
    if(c=='-')
    {
        flag=0;
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}

//--------------------------------------------------

ll fac[maxn];
ll inv[maxn];
ll qlow(ll a,ll n){
    ll ans=1;
    while(n){
        if(n&1)ans=(ans*a)%mod;
        a=(a*a)%mod;
        n>>=1;
    }
    return ans;
}
void init(){
    inv[0]=fac[0]=1;
    for(int i=1;i<=1000000;i++){
        fac[i]=(fac[i-1]*i)%mod;
        inv[i]=qlow(fac[i],mod-2);
    }
}
ll cal(ll n,ll m){
    if(n<=1000000){
        return fac[n]*inv[m]%mod*inv[n-m]%mod;
    }
    ll ans=inv[m];
    for(int i=0;i<m;i++)
        ans=(ans*(n-i))%mod;
    return ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int t;
    init();
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        ll n,m,k;
        scanf("%lld %lld %lld",&n,&m,&k);
        ll ans=cal(m,k);
        if(n==1){
            printf("Case #%d: %lld\n",cas,ans);
            continue;
        }
        ll tmp=0;
        for(ll i=k,j=1;i>=2;i--,j++){
            ll tt=cal(k,i)*i%mod*qlow(i-1,n-1)%mod;
            if(j&1)tmp=(tmp+tt)%mod;
            else tmp=(tmp-tt+mod)%mod;
        }
        printf("Case #%d: %lld\n",cas,(tmp*ans)%mod);
    }
    return 0;
}


容斥告一段落了,没想到准备很快撸完的专题,撸了这么久,这几天真是浪啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值