1. 程式人生 > >容斥原理簡單的入門題總結

容斥原理簡單的入門題總結

容斥原理

這裡對容斥原理進行簡單的總結,容斥原理主要用於求n個數能組成的乘積種類數,從這之中我們就可以引申出容斥原理的很多用法,對於給定數字求組合種類的題目,我們就要想到用lcm去運算,對於給定數求互質/不互質時,我們就要想到對給定的數進行質因數分解。下面給出一些容斥原理的入門題。

容斥原理第一題

HDU-2204- Eddy’s愛好
題意就是給出一個數n,問1-n中有多少個數可以表示為m^k,m,k均為正整數且k>1
由於這裡k是大於1的,所以我們想一下哪些k是可以被替代的,例如一個數如果可以被表示為

m 4 ,那麼他一定可以被表示為 ( m 2 ) 2 ,所以我們知道了只要列舉所有質數次冪的組合就可以了,同樣當我們列舉2的整數次冪和3的整數次冪都會列舉到6,所以我們需要容斥一下,那麼容斥的個數上界是多少呢,我們發現2*3*5*7=210>60,而2^60>1e18,所以容斥的個數上界是3,容斥列舉的質數上界為59。
容斥原理第一題程式碼

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int prime[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59};
long long n,ans;
int i;//全域性變量表示當前列舉的容斥是幾個數的。
void dfs(int pos,int num,int p)
{
    if(p==0)
    {
        long long tmp=pow(n,1.0/num);
        if
(pow(tmp,(double)num)>n) tmp--;//注意精度 tmp--; if(tmp>0) { if(i&1) ans+=tmp; else ans-=tmp; } return ; } if(pos==17) return; if(num*prime[pos]<60) dfs(pos+1,num*prime[pos],p-1); dfs(pos+1,num,p); return ; } int main() { while(scanf("%lld",&n)!=EOF) { ans=0; for(i=1;i<=3;i++) dfs(0,1,i); printf("%lld\n",ans+1); } return 0; }

容斥原理第二題

HDU-3208-Integer’s Power
題意就是定義 p o w e r ( x ) x 能被表示為 n k 中最大的 k ,例如 p o w e r ( 9 ) = 2 , p o w e r ( 32 ) = 5 , p o w e r ( 18 ) = 1 ,給出 l , r 計算

Σ r i = l p o w e r ( i )
很明顯可以將題意轉化為
Σ r i = 1 p o w e r ( i ) Σ l 1 i = 1 p o w e r ( i )
由於這題要求可以被表示的最大次冪,我們就不能像上一題一樣容斥,因為某些數表示為 m 4 比表示為 ( m 2 ) 2 更優,所以我們要先算出所有數作為次冪可能的出現次數,由於 2 6 0 > 10 1 8 所以我們只需要算到60即可,在預處理出這個陣列之後,我們就可以進行容斥,這道題要怎麼容斥呢,我們想一下比如我們計算6作為次冪出現的次數為 d p [ 6 ] ,那麼這之中肯定已經包含了 d p [ 2 ] d p [ 3 ] 中的一部分,相對於2,3,我們肯定選擇6,因為他更大,所以我們要讓 d p [ 2 ] = d p [ 6 ] , d p [ 3 ] = d p [ 6 ] ,很明顯我們要對某個數的所有因子項減去當前數的出現次數,怎麼保證每個陣列做貢獻的時候是最終狀態呢,我們只要逆序去dp就可以了,這樣就保證了每個陣列去更新其他陣列的時候也自己已經是最終狀態。另外這題利用 p o w ( x , 1 / y ) 去計算 x y 具有精度問題,所以要在求出之後左右判一下精度問題。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll dp[63];
ll pow_(ll x,int n)
{
    ll ans=1;
    while(n)
    {
        if(n&1)
        {
            double tmp=1.0*INF/ans;
            if(x>tmp) return -1;
            ans*=x;
        }
        n>>=1;
        if(x>((ll)1<<31)&&n) return -1;
        x=x*x;
    }
    return ans;
}
ll cal(ll x,ll n)//返回精確的\sqrt[y]{x}
{
    ll a=(ll)pow(x,1.0/n);
    ll qt=pow_(a,n);
    ll ql=pow_(a-1,n);
    ll qr=pow_(a+1,n);
    if(qr!=-1&&qr<=x) return a+1;
    else if(qt!=-1&&qt<=x) return a;
    else if(ql!=-1&&ql<=x) return a-1;
}
ll solve(ll n)
{
    if(n==1) return 1;
    int i,j;
    memset(dp,0,sizeof(dp));
    dp[1]=n;
    for(i=2;i<63;i++)
    {
        dp[i]=cal(n,i)-1;
        if(dp[i]==0) break;
    }
    int k=i;
    for(i=k-1;i>0;i--)//逆序DP
    {
        for(j=1;j<i;j++)
        {
            if(i%j==0) dp[j]-=dp[i];
        }
    }
    ll ans=0;
    for(int i=1;i<k;i++) ans+=1LL*i*dp[i];
    return ans;
}
int main()
{
    ll l,r;
    while(scanf("%lld%lld",&l,&r)!=EOF)
    {
        if(l==0&&r==0) break;
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}

容斥原理第三題

HDU-1796-How many integers can you find
題意就是給出一個整數n,一個具有m個元素的陣列,求出1-n中有多少個數至少能整除m陣列中的一個數
這道題就是經典的給出某個陣列去組合的問題,只要對當前選中元素取lcm即可,但是要注意這題m陣列會出現0,而且算術過程中ans,lcm可能會超過int,最好全程用long long
容斥原理第三題程式碼

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 205;
int a[maxn];
int m,i;
long long ans;
long long n;
int gcd_(int a, int b)
{
     return b ==0? a : gcd_(b, a % b);
}
int lcm_(int a, int b)
{
     return a / gcd_(a, b) * b;
}
void dfs(int pos,int num,int p)
{
    if(p==0)
    {
        if(num==0) return ;//注意0要特判掉
        long long tmp=(n-1)/num;
        if(i&1) ans+=tmp;
        else ans-=tmp;
        return ;
    }
    if(pos==m) return ;
    if(lcm_(num,a[pos])<n) dfs(pos+1,lcm_(num,a[pos]),p-1);
    dfs(pos+1,num,p);
    return ;
}
int main()
{
    while(scanf("%lld%d",&n,&m)!=EOF)
    {
        ans=0;
        for(int i=0;i<m;i++) scanf("%d",&a[i]);
        for(i=1;i<=m;i++) dfs(0,1,i);
        printf("%lld\n",ans);
    }
    return 0;
}

容斥原理第四題

HDU-2841-Visible Trees
這道題題意就是給出第一象限的n*m個點,求出站在原點可以看見多少個點
將題意稍微轉化一下就變成了,求 ( a , b ) 1 <= a <= n , 1 <= b <= m , g c d ( a , b ) = 1 ) 的點對數,由於此題n,m範圍均為1e5,所以我們只要列舉a,計算1-m範圍內有多少個與a互質的數就可以了,可以發現這是個經典問題,所以就解決了。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
vector<int> v;
int cal(int n,int x)//計算1-n中有多少個與x互質的數
{
    int ans=0;
    v.clear();
    for(int i=2;i*i<=x;i++)
    {
        if(x%i==0)
        {
            v.push_back(i);
            while(x%i==0) x/=i;
        }
    }
    if(x>1) v.push_back(x);
    int sz=v.size();
    for(int i=0;i<(1<<sz);i++)
    {
        int num=0;
        int tmp=1;
        for(int j=0;j<sz;j++)
        {
            if(i&(1<<j))
            {
                num++;
                tmp*=v[j];
            }
        }
        tmp=n/tmp;
        if(num&1) ans-=tmp;
        else ans+=tmp;
    }
    return ans;
}
int main()
{
    int n,m;
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        long long ans=0;
        for(int i=1;i<=n;i++)
        {
            ans+=cal(m,i);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

容斥原理第五題

ZOJ-2836-Number Puzzle
題意就是給出一個整數m,一個具有n個元素的陣列,求出1-m中有多少個數至少能整除n陣列中的一個數
這道題就是經典的給出某個陣列去組合的問題,只要對當前選中元素取lcm即可
容斥原理第五題程式碼

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 205;
int a[maxn];
int m,i;
long long ans;
long long n;
int gcd_(int a, int b)
{
     return b ==0? a : gcd_(b, a % b);
}
int lcm_(int a, int b)
{
     return a / gcd_(a, b) * b;
}

void dfs(int pos,int num,int p)
{
    if(p==0)
    {
        if(num==0) return ;
        long long tmp=n/num;
        if(i&1) ans+=tmp;
        else ans-=tmp;
        return