容斥原理专题二

下个月就要打区域赛了。加油!加油!最后一个月我要努力最后冲刺一波!



第一题 poj-1091

分析:看完题目,我们首先猜测要使得跳蚤最后可以左跳一个单位,那么这N+1个数字一定满足gcd(x1,x2,…,xn,M)==1,不然的话一定不能躲开他们之间的公共因子。转换一下等价题意:有多少种方案使得gcd(x1,x2,…xn,M)==1而且1<=xi<=M。正面解决似乎不是特别简单,我们转换一下思路,考虑它的逆命题。gcd不为1的情况应该怎么解决,我们将所有情况(M^N)减去这个情况就是要求的情况。我们分类枚举gcd不为1的情况,gcd一定是M的一个因子,那么我们先将M分解成M=P1^a1*P2^a2…Pk^ak,然后一个一个枚举M的质因数。当gcd为P1的时候,那么其它的n个数里面必须也有p1的因子,那么可以选择的就有M/P1个数,同时n个数是可以重复而且是有顺序的,那么总的方案数就是(M/P1)^n。依此类推求出gcd为P2、P3、。。的情况。有人会问,为什么一定是P1,而不会是P1^2或者P1的更高次呢,如果gcd是P1的更高次,那么这种情况一定是被包括在gcd为P1里面的,所以不需要考虑。但是,有一点,那就是P1*P2的情况。如果是这样的话,那么这个情况同时被P1和P2计算了一次,所以要减去,这用到了容斥原理,其他情况类同。

答案比较大,请使用高精度。

#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   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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

//compare比较函数:相等返回0,大于返回1,小于返回-1  
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();  
    //前面补0,弄成长度相同  
    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'));//去除结果中多余的前导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;  
}
string calc(ll n){
    string ans;
    while(n){
        ans=(char)(n%10+'0')+ans;
        n/=10;
    }
    return ans;
}
int tot=0;
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;
        }
    }
}
vector<ll>p;
void cal(ll n){
    for(int i=0;i<tot&&prime[i]<=n;i++)
        if(n%prime[i]==0){
            p.PB(prime[i]);
            while(n%prime[i]==0)
                n/=prime[i];
        }
    if(n>1)p.PB(n);
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    init(10000);
    int n;
    ll m;
    while(~scanf("%d %lld",&n,&m)){
        string M=calc(m);
        string ans=M;
        for(int i=1;i<n;i++)
            ans=mul(ans,M);
        p.clear();
        cal(m);
        for(int i=1;i<(1ll<<p.size());i++){
            ll mult=1,num=0;
            for(int j=0;j<p.size();j++)
                if(i&(1<<j)){
                    mult*=p[j];
                    num++;
                }
            int t=m/mult;
            string T=calc(t);
            string tmp=T;
            //cout<<T<<endl;
            for(int i=1;i<n;i++)
                tmp=mul(tmp,T);
            if(num&1)ans=sub(ans,tmp);
            else ans=add(ans,tmp);
        }
        cout<<ans<<endl;
    }
    return 0;
}


第二题 poj-3904

分析:给你N个数,问你能找出多少组4个数组成的数组,使得这四个数的gcd为1。做到现在,我们应该已经麻木了。按照套路,正面做不好做,所以考虑将所有情况(C(n,4))减去四个数的gcd不为1的情况。那么枚举gcd一直到小于n个数里面最大数的最大素数,当gcd为Pi(某个素数)的时候,找出有多少个数能被Pi整除,假设有k个,那么方案数就是C(k,4)(当k小于4的时候,方案数为0)。你问为什么是Pi,不是Pi的高次,我答肯定被包括在Pi里面。然后考虑Pi*Pj的情况,用到容斥原理,需要减去这种情况,多个P类似。

这个思路显然是没有问题的,但是这个算法显然是超时的。因为这里的N个数是离散的,不是像之前那样连续的,不是很方便求有多少个数能被Pi整除,一个一个求的话显然不现实。这里做一个预处理,将N个数的每个数分解,将他们质因数的一次方作为因子遍历的标准(为什么不要高次方,前一段话应该已经解释过了),将他们的“所有”因子记录下来,同时记录每个因子含有的质因子个数(方便判断之后是加还是减),最后遍历1~10000之间的记录,分别计算并累加结果。当然,你也是可以不遍历1~10000的,可以按照上面说的遍历1~10000之间的素数。

#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   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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

int dat[maxn];
int ct[maxn];
int sum[maxn];
ll cal(int n){
    return 1ll*n*(n-1)*(n-2)*(n-3)/24;
}
void calc(int n){
    vector<int>p;
    for(int i=2;i*i<=n;i++)
        if(n%i==0){
            p.PB(i);
            while(n%i==0)
                n/=i;
        }
    if(n>1)p.PB(n);
    for(int i=1;i<(1<<p.size());i++){
        int mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        ct[mult]++;
        sum[mult]=num;
    }
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n;
    while(~scanf("%d",&n)){
        clr(ct,0);
        clr(sum,0);
        for(int i=0;i<n;i++){
            scanf("%d",&dat[i]);
            calc(dat[i]);
        }
        ll ans=cal(n);
        for(int i=1;i<=10000;i++)
            if(ct[i]>=4){
                if(sum[i]&1)ans-=cal(ct[i]);
                else ans+=cal(ct[i]);
            }
        printf("%lld\n",ans);
    }
    return 0;
}


第三题 Uva-10721

分析:转换一下题意哈:求x1+x2+…+xk=n而且1<=xi<=m的解的个数。中规中矩的一道题,是一道数学题,很简单。转换一下就是前一个专题的一道题,x1+x2+…+xk=n-k而且0<=xi<=m。不多说了,直接上答案:C(n-1,k-1)-C(k,1)C(n-1-m,k-1)+C(k,2)C(n-1-m*2,k-1)-…….一直累加到n-1-m*i小于k-1为止。

poj-1173是这道题的加强版,之后还需要用到数位DP的样子(唉,太麻烦,我就不想写,而且DP也不是我的活,=。=)。

#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          50+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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

ll dat[maxn][maxn];
void init(){
    for(int i=0;i<=50;i++)
        dat[i][0]=dat[i][i]=1;
    for(int i=2;i<=50;i++)
        for(int j=1;j<i;j++)
            if(j<=i/2)dat[i][j]=dat[i-1][j]+dat[i-1][j-1];
            else dat[i][j]=dat[i][i-j];
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n,k,m;
    init();
    while(~scanf("%d %d %d",&n,&k,&m)){
        if(n<k||k>n*m){
            puts("0");
            continue;
        }
        ll ans=dat[n-1][k-1];
        for(int i=1;n-i*m>=k;i++){
            if(i&1)ans-=dat[k][i]*dat[n-1-i*m][k-1];
            else ans+=dat[k][i]*dat[n-1-i*m][k-1];
        }
        printf("%lld\n",ans);
    }
    return 0;
}


第四题 poj-3695

分析:说一下题意哈:给你n个矩形(左下角坐标和右上角坐标)和m个询问,每个询问是求特定一些矩形的交集面积。矩形与矩形的交集肯定还是矩形,那么我们就维护一个交集矩形,最后求出交集矩形的面积即可。这里可能有多个矩形复杂交,那么我们回忆容斥原理的图形表示,发现这道题目可以使用容斥。

这道题目我们用位运算法来写是肯定会超时的,因为他一点剪枝没有,需要将所有矩阵进行计算。这里推荐dfs的写法,当矩阵的面积成为0的时候,就不用再搜索下去了(因为没有意义),大大的节约了时间。

#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   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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

struct rec{
    int x1,y1,x2,y2;
}dat[30];
int str[maxn];
bool bing(rec& a,rec b){
    rec t;
    if(a.x1>b.x1)swap(a,b);
    if(b.x1<a.x2&&b.y1<a.y2&&b.y2>a.y1){
        t.x1=b.x1;
        t.y1=(b.y1<a.y1)?a.y1:b.y1;
        t.y2=(b.y2>a.y2)?a.y2:b.y2;
        t.x2=(b.x2>a.x2)?a.x2:b.x2;
        swap(t,a);
        return false;
    }
    else return true;
}
ll cal(rec& t){
    return 1ll*(t.x2-t.x1)*(t.y2-t.y1);
}
ll dfs(int n,rec t,int num){
    if(n==-1){
        if(num){
            if(num&1) return cal(t);
            else return -1*cal(t);
        }
        return 0;
    }
    ll ans=dfs(n-1,t,num);
    if(bing(t,dat[str[n]-1]))return ans;
    ans+=dfs(n-1,t,num+1);
    return ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n,m,cas=1;
    while(scanf("%d %d",&n,&m),n||m){
        printf("Case %d:\n",cas++);
        for(int i=0;i<n;i++)
            scanf("%d %d %d %d",&dat[i].x1,&dat[i].y1,&dat[i].x2,&dat[i].y2);
        for(int tcas=1;tcas<=m;tcas++){
            int r;
            scanf("%d",&r);
            for(int i=0;i<r;i++)
                scanf("%d",&str[i]);
            rec t;
            t.x1=-1;t.y1=-1;t.x2=1001;t.y2=1001;
            printf("Query %d: %lld\n",tcas,dfs(r-1,t,0));
        }
        puts("");
    }
    return 0;
}


第五题 poj-2773

分析:这是一道有变化的题目,是求与2006互质的第m个数。拿到题目我们首先是懵逼的,因为新的题目,完全没有思路。那我们看看我们会什么,我们会求从1~n之间与2006互质的数有多少个,那么我们转化一下,1~n之间与2006互质的数有m个(m已知),我们会求吗?我们发现我们是会的,要么就是枚举n,要么就是利用二分的方法求出n。但是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          100000+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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

ll solve(ll r,ll n){
    vector<ll>p;
    for(ll i=2;i*i<=n;i++)
        if(n%i==0){
            p.PB(i);
            while(n%i==0)
                n/=i;
        }
    if(n>1)p.PB(n);
    ll ans=0;
    for(int i=1;i<(1ll<<p.size());i++){
        ll mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        if(num&1)ans+=r/mult;
        else ans-=r/mult;
    }
    return r-ans;
}
ll bs(int n,int k){
    ll l=1,r=1ll*inf*inf;
    while(l<=r){
        ll m=(l+r)>>1;
        if(solve(m,n)>=k)r=m-1;
        else l=m+1;
    }
    return l;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int m,k;
    while(~scanf("%d %d",&m,&k)){
        printf("%lld\n",bs(m,k));
    }
    return 0;
}


第六题 uvalive-4683

分析:这道题又和前面的题目不一样,简单说下题意:给你k个数(保证这k个数不能互相整除),找出第n个只能被这k个数里面的某一个数整除的数(不能被2个或者2个以上的数整除)。我们看到要求第n个数,那么就知道这又是二分。那么怎么二分呢?就是求1~m之间只能被这k个数里面的某一个数整除的数的个数为n,而且m是满足条件里面的最小整数就是我们要求的答案。大致思路没有问题了,那么要找怎么求解只能被k个数里面的某一个数整除,而不能被多个数整除的数的个数。我们从正面和反面考虑都没有好的办法,那么这时借助图像我们有新的发现。其实我们要求的是圆与圆之间没有相交部分(只有一层的地方)的集合,我们考虑容斥原理(求所有圆包括的面),发现多的部分相应的减去,那么对于每一个小的子集来说就是乘以一个里面包含的元素个数然后再来容斥的相加(结论是我猜的,但是可以用二项式定理来证明)。

正面做的话或超时,因为每次都将这k个数算了一边lcm太慢了,可以只算一遍然后存起来。

#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          5000+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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

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


第七题 zoj-3556

分析:题意十分明了,不读题了。并集是空集,那么就考虑反面(正面先考虑,发现十分之难),用所有情况(2^nk)减去并集不为空的情况,并集不为空,那么枚举并集里面的元素个数。有一个的时候,那么首先从n个里面选出这一个元素(C(n,1)),然后考虑除去这个元素(因为每个集合都必须带这个元素)有多少中子集,发现有2^(n-1),那么总的方案数就是C(n,1)*2^k(n-1);之后依此类推。。。

最后得到结论2^nk-C(n,1)* 2^k(n-1)+C(n,2)* 2^k(n-2)-……+*(-1)^n*C(n,n)*2^0

利用二项式定理化简一下得到(2^k-1)^n



第八题 hdu-4135

分析:非常经典的容斥原理题目(可能是最基础的)。在PDF里面有详细的介绍和代码,这里不再赘述。

#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   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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

ll solve(ll r,ll n){
    if(r==0)return 0;
    vector<ll>p;
    for(ll i=2;i*i<=n;i++)
        if(n%i==0){
            p.PB(i);
            while(n%i==0)
                n/=i;
        }
    if(n>1)p.PB(n);
    ll ans=0;
    for(int i=1;i<(1<<p.size());i++){
        ll mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        if(num&1)ans+=r/mult;
        else ans-=r/mult;
    }
    return r-ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    ll n,a,b;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++){
        scanf("%lld %lld %lld",&a,&b,&n);
        printf("Case #%d: %lld\n",cas,solve(b,n)-solve(a-1,n));
    }
    return 0;
}


第九题 hdu-4336

分析:先说下题意把:开干脆面收集卡牌,一共有N张不同卡牌,第i卡牌收集到的概率是pi,问要收集齐这N张卡牌需要买干脆面的包数的期望值。首先这道题确实不太会,我是看完别人的题解才知道怎么做的(主要是期望啊、概率啊学得不太好)。这里我贴一下别人的理解:E1表示买买到1的期望,E1 = 1/p1,也就是说E1包里面肯定包含1这张卡片,当我们计算E1和E2时,是不是会有一种情况:我们想要卡片1的时候已经买到了卡片2,然后我们又要计算买卡片2的期望,正是因为这样的交集使得我们可以用容斥,交集的期望E12 = 1 / (p1 +p2) 表示肯定买到1、2中的其中一包,E123就表示肯定买到1、2、3中的某一种,我们在计算E12,E13,E23的时候E123多减了一次,要加回来,以此类推….

#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             20+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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

double dat[maxn];
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int n;
    while(~scanf("%d",&n)){
        for(int i=0;i<n;i++)
            scanf("%lf",&dat[i]);
        double ans=0;
        for(int i=1;i<(1<<n);i++){
            double mult=0.0;
            int num=0;
            for(int j=0;j<n;j++)
                if(i&(1<<j)){
                    mult+=dat[j];
                    num++;
                }
            if(num&1)ans+=1.0/mult;
            else ans-=1.0/mult;
        }
        printf("%.4f\n",ans);
    }
    return 0;
}


第十题 hdu-4059

分析:题意还是很好理解的:给你一个n,让你求所有小于n且与n互质的数的4次方的和。小于n且与n互质是老套路了,这里多加了一个条件4次方,但是还是按照以前那么考虑。现将n分解,然后按照n的每个质因数来容斥。这道题只有一个问题:1^4+2^4+3^4+……+n^4=?怎么求。如果百度的话,可以得到n*(n+1)(2*n+1)(3*n*n+3*n-1)/30(30可以使用逆元!),那么问题就解决了。万一比赛的时候,不能百度怎么办。那么可以试试矩阵快速幂,虽然我写炸了。

还有这道题取模和精度要特别小心

#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             10+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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

ll qlow(ll a,ll b,ll m){
    a%=m,b%=m;
    ll ans=1;
    while(b){
        if(b&1)ans=(ans*a)%m;
        a=(a*a)%m;
        b>>=1;
    }
    return ans;
}
ll cal(ll n){
    n%=mod;
    ll cnt=n*(n+1)%mod*(2*n+1)%mod*(3*(n*n)%mod+3*n-1)%mod*qlow(30,mod-2,mod)%mod;
    return cnt;
}
ll solve(ll r,ll n){
    vector<ll>p;
    for(ll i=2;i*i<=n;i++)
        if(n%i==0){
            p.PB(i);
            while(n%i==0)
                n/=i;
        }
    if(n>1)p.PB(n);
    ll ans=cal(r);
    for(int i=1;i<(1ll<<p.size());i++){
        ll mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        ll t=mult*mult%mod*mult%mod*mult%mod*cal(r/mult)%mod;
        if(num&1)ans=(ans-t+mod)%mod;
        else ans=(ans+t)%mod;
    }
    return ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        ll n;
        scanf("%lld",&n);
        printf("%lld\n",solve(n,n));
    }
    return 0;
}


第十一题 hdu-4407

分析:题意:先将1~n这n个数按顺序排成一行,然后进行多次操作(m次)。操作1:求从第x个数到第y个数之间与p互质的数的和。操作2:将第x个数改成c。拿到题目一看我是懵逼的,虽然知道x~y之间与p互质的数的和怎么求,但是操作2会将数字改变,那就不能一下子求了。然后,我冷静下来,发现像这样的从x到y之间与p互质我只学过容斥原理,而且像这样按顺序排列的用容斥原理来求也非常的好求。那么要解决的就只有操作2了,怎么将无序改成有序。我发现改掉的数字十分之少,完成可以单独分开来判断。也就是说,将每次操作2的修改用map记录下来,先按照一开始1~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             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;

/*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;
        c=getchar();
    }
    while(c<='9'&&c>='0')
    {
        x=x*10+c-'0';
        c=getchar();
    }
    if(!flag)x=-x;
}*/

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

map<int,int>mp;
ll cal(int n,int k){
    int num=n/k;
    return 1ll*num*(num+1)/2*k;
}
ll solve1(int r,int n){
    if(r==0||n==1)return 0;
    vector<int>p;
    for(int i=2;1ll*i*i<=n;i++)
        if(n%i==0){
            p.PB(i);
            while(n%i==0)
                n/=i;
        }
    if(n>1)p.PB(n);
    ll ans=0;
    for(int i=1;i<(1ll<<p.size());i++){
        int mult=1,num=0;
        for(int j=0;j<p.size();j++)
            if(i&(1<<j)){
                mult*=p[j];
                num++;
            }
        if(num&1)ans+=cal(r,mult);
        else ans-=cal(r,mult);
    }
    return ans;
}
ll solve(int a,int b,int p){
    ll ans=1ll*(a+b)*(b-a+1)/2;
    ans-=solve1(b,p)-solve1(a-1,p);
    for(map<int,int>::iterator it=mp.begin();it!=mp.end();it++)
        if((it->first)>=a&&(it->first)<=b){
            if(__gcd(it->first,p)==1)ans-=(it->first);
            if(__gcd(it->second,p)==1)ans+=(it->second);
        }
    return ans;
}
int main(){
    //freopen("d:\\acm\\in.in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        mp.clear();
        int n,m;
        scanf("%d %d",&n,&m);
        while(m--){
            int op,x,y,c,p;
            scanf("%d",&op);
            if(op==1){
                scanf("%d %d %d",&x,&y,&p);
                printf("%lld\n",solve(x,y,p));
            }
            else {
                scanf("%d %d",&x,&c);
                mp[x]=c;
            }
        }
    }
    return 0;
}

这篇博文的前半篇是在上周五写的,后半篇是周一晚上写的。最近事情比较多,但是我还是想一边打比赛一边把所有题解记录下来。上周六的大连网络赛和上周日的CCF认证、玲珑杯,我都会把题解写出来的!(虽然我大部分题目都不会=。=)

哦 这篇容斥不是最后一篇,应该还会有一篇的样子(那个应该是最后一篇),大概5、6题目的样子,稍微少一点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值