容斥原理 专题

poj 3904 Sky Code

这个题目是不错的容斥原理的题目。

题意: 给定最多1w个数,每个数不超过1w,要求从中选择4个数a,b,c,d满足gcd(a,b,c,d)==1 的个数

思路: 直接暴力的话是 时间复查度是O(10000^4*log(10000))肯定会超时

           容斥原理: 从n个数中任意选择4个C(4,n) -  ( 从n个中选择4个使得gcd至少为2 )  -   ( 从n个中选择4个使得gcd至少为3)  + ( 从n个中选择4个使得gcd至少为6)  ……具体实现就是每个数,素因子分解,然后用二进制枚举其所有的因子

这个题目要注意的是,答案可能超过int,所有要用long long ,因为这还wa了一次

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int maxn=10010;
int prime[maxn],num,cnt[maxn],p[20],ok[maxn];
bool isprime[maxn];
typedef long long ll;

void init()
{
    for(int i=2;i<maxn;i++)
       if(!isprime[i])
       {
           prime[num++]=i;
           ok[i]=1;
           for(int j=2*i;j<maxn;j+=i)
              isprime[j]=1,ok[j]++;
       }
}
ll C(int a,int b)
{
    ll ret=1;
    for(int i=1;i<=b;i++)
      ret=ret*(a-i+1)/i;
    return ret;
}
int main()
{
    init();
    int n,tmp;
    while(scanf("%d",&n)==1)
    {
        memset(cnt,0,sizeof(cnt));
        int Max=0;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&tmp);
            Max=max(Max,tmp);
            int ct=0;
            for(int j=0;prime[j]*prime[j]<=tmp;j++)
               if(tmp%prime[j]==0)
               {
                   p[ct++]=prime[j];
                   while(tmp%prime[j]==0) tmp/=prime[j];
               }
            if(tmp!=1) p[ct++]=tmp;
            int lim=1<<ct;
            for(int j=0;j<lim;j++)
            {
                int mul=1;
                for(int r=0;r<ct;r++)
                   if(j&(1<<r)) mul*=p[r];
                cnt[mul]++;
            }
        }
        ll ans=0;
        for(int i=1;i<=Max;i++)
          if(ok[i]%2==0) ans+=C(cnt[i],4);
          else ans-=C(cnt[i],4);
        printf("%I64d\n",ans);
    }
    return 0;
}

UVA #10325 "The Lottery" [难度:简单]


这个题目之前都做过,题意是给出【 L ,R】 区间L <= R  <= 2^31 ,和M个数(m<=15)求出【L,R】 区间范围类有多少个,不被这m个数任意一个数整除的数有多少个

思路: 求【1,x】 : ans =  x - x / a1 - x/a2 -……- x/an +  x/ lcm(a1,a2) + x/ lcm(a1,a3) + x/ lcm(a2,a3)   



UVA #11806 "Cheerleaders" [难度:简单]

这个题目不知道为什么就是没有过,思路: 是C(n*m , k)  - C(n*m - n ,k)*2 - C(n*m-m,k)*2 + C(n*m -n -m ,k)……  待思考



      TopCoder SRM 477 "CarelessSecretary" [难度:简单]

题意:是给出N个不同的信,然后这N个信对应不同的N个官员,恰巧指定K官员都受到错误的信,问这样情况共有几种可能?这个题目样例给的很强,我自己在纸上算了好几个样例都过了,TC上交题目很麻烦我就没有做。思路: N! - C(K,1)* (N-1)! + C(K,2)*(N-2)! - C(K,3)*(N-3)! ……


TopCoder TCHS 16 "Divisibility" [难度:简单]


跟 “The Lottery ”几乎是一样的题目



spo Another Game With Numbers 简单题目,用容斥原理求出【1,n】区间范围类不被 给定m个数都整除的个数


 TopCoder SRM 382 "CharmingTicketsEasy" [难度:中等]

   这个题目是挺好的题目,开始使劲的忘容斥原理方面想就是想不到怎么容斥,后来才知道其实主体思想还是应该用dp,先用dp算出满足第一个条件的个数 + dp算出满足第二个条件的个数 - 同时满足两个条件的个数

状态:f[ len ][ sum ]  表示长度为len组成各个数的和为sum的个数  dp[ len ][ sum1 ][ sum2 ] 表示长度为len 奇数为和为sum1 偶数位和为sum2 的个数,我是用的滚动数组内存优化

const int mod=999983;
long long dp[2][500][500],f[51][500];

class CharmingTicketsEasy
{
        public:
        int count(int K, string good)
        {
                int dig[10],len=good.length();
                for(int i=0;i<len;i++) dig[i]=good[i]-'0';
                sort(dig,dig+len);
                int lim=dig[len-1]*K;
                memset(f,0,sizeof(f));
                memset(dp,0,sizeof(dp)) ;
                f[0][0]=1;
                for(int i=0;i<K;i++)
                for(int j=0;j<=lim;j++)
                  if(f[i][j]){
                     for(int r=0;r<len;r++) f[i+1][j+dig[r]]=(f[i+1][j+dig[r]]+f[i][j])%mod;
                  }
              // for(int i=0;i<=lim;i++)
              //    cout<<i<<" "<<f[K][i]<<endl;
                dp[0][0][0]=1;
                int now=0;
                for(int i=0;i<K;i++)
                {
                  for(int j=0;j<=lim;j++)
                  for(int r=0;r<=lim;r++)
                  if(dp[now][j][r])
                  {
                      if(i&1){
                         for(int k=0;k<len;k++)
                           dp[now^1][j][r+dig[k]]=(dp[now^1][j][r+dig[k]]+dp[now][j][r])%mod;
                      }else{
                         for(int k=0;k<len;k++)
                           dp[now^1][j+dig[k]][r]=(dp[now^1][j+dig[k]][r]+dp[now][j][r])%mod;
                      }
                  }
                  memset(dp[now],0,sizeof(dp[now]));
                  now=1-now;
                }
              long long ans=0;
              for(int i=0;i<=lim;i++) ans=(ans+f[K][i]*f[K][i])%mod;
              ans=(ans+ans)%mod;
              for(int i=0;i<=lim;i++)
              for(int j=0;j<=lim;j++)
                if(dp[now][i][j])
                {
                     for(int r=0;r<=lim;r++)
                       if(j+r>=i&&dp[now][r][j+r-i]&&i+j==j+2*r-i)
                         ans=((ans-dp[now][i][j]*dp[now][r][j+r-i])%mod+mod)%mod;
                }
            //cout<<ans<<endl;
            return ans;
        }

 TopCoder SRM 390 "SetOfPatterns"  [难度:中等]

 这个题目真是坑爹呀,说是容斥原理,但是我用容斥原理怎么都没想出来怎么容斥。看了别人的dp代码,dp[ i ][ j ] 表示匹配到每个串的第i个字符 匹配的集合为j(状态压缩)的个数,先预处理,satisfy[ i ] [ j ] 表示在第i字符为 j 所能够匹配的的集合

#line 7 "SetOfPatterns.cpp"
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <fstream>
#include <numeric>
#include <iomanip>
#include <bitset>
#include <list>
#include <stdexcept>
#include <functional>
#include <utility>
#include <ctime>
using namespace std;

#define PB push_back
#define MP make_pair

#define REP(i,n) for(i=0;i<(n);++i)
#define FOR(i,l,h) for(i=(l);i<=(h);++i)
#define FORD(i,h,l) for(i=(h);i>=(l);--i)

typedef vector<int> VI;
typedef vector<string> VS;
typedef vector<double> VD;
typedef long long LL;
typedef pair<int,int> PII;
const int mod=1000003;
int bits[1<<15],dp[2][1<<15],satisfy[50+1][26];

class SetOfPatterns
{
        public:

        inline void add(int &a,int b)
        {
            if((a+=b)>=mod) a-=mod;
        }
        inline int equal(char a,char b)
        {
            return a==b|| a=='?';
        }
        inline int bitCount(int m)
        {
            return bits[m];
        }
        inline int ones(int n)
        {
            return (1<<n)-1;
        }
        int howMany(vector <string> patterns, int k)
        {
               int n=patterns.size();
               int m=patterns[0].size();
               bits[0]=0;
               for(int i=0;i<(1<<n);++i)
                  bits[i]=bits[i>>1]+ (i&1);
              // cout<<bits[3]<<endl;
               memset(satisfy,0,sizeof(satisfy));
               for(int i=1;i<=m;i++)
               for(char c='a';c<='z';c++)
               for(int j=0;j<n;j++)
                  if(equal(patterns[j][i-1],c))
                     satisfy[i][c-'a']|=(1<<j);
               memset(dp,0,sizeof(dp));
               dp[0][ones(n)]=1;
               for(int i=1;i<=m;i++)
               {
                   memset(dp[i&1],0,sizeof(dp[i&1]));
                   for(int j=0;j<(1<<n);j++)
                     if(dp[(i+1)&1][j]>0&&bitCount(j)>=k)
                      for(int c=0;c<26;c++) add(dp[i&1][j&satisfy[i][c]],dp[(i+1)&1][j]);
               }
               int res=0;
               for(int i=0;i<(1<<n);i++)
                 if(bitCount(i)==k) add(res,dp[m&1][i]);
               return res;
        }

 TopCoder SRM 176 "Deranged" [难度:中等]

思路: 用二进制枚举,枚举不动点的集合,其他的数自由排列,然后如果不动点的个数为偶数则加 ,否则减去。。 注意自由排列涉及到相同的元素


LL fact[16];
int cnt[16];

class Deranged
{
        public:

        long long numDerangements(vector <int> nums)
        {
             int N=nums.size();
             fact[0]=1;
             for(int i=1;i<=N;i++)
               fact[i]=i*fact[i-1];
             LL res=0;
             for(int m=0;m<(1<<N);m++)
             {
                 int free=0;
                 memset(cnt,0,sizeof(cnt));
                 for(int i=0;i<N;i++)
                    if((m&(1<<i))!=0)
                      free++,cnt[nums[i]]++;
                 LL a=fact[free];
                 for(int i=0;i<N;i++) a/=fact[cnt[i]];

                 if((N-free)%2==0) res+=a;
                 else res-=a;
             }
             return res;
        }




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值