1. 程式人生 > >HDU-1796 How many integers can you find(容斥原理)

HDU-1796 How many integers can you find(容斥原理)

                            How many integers can you find

                          Time Limit: 12000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
                                                  Total Submission(s): 11704    Accepted Submission(s): 3506

Problem Description

  Now you get a number N, and a M-integers set, you should find out how many integers which are small than N, that they can divided exactly by any integers in the set. For example, N=12, and M-integer set is {2,3}, so there is another set {2,3,4,6,8,9,10}, all the integers of the set can be divided exactly by 2 or 3. As a result, you just output the number 7.

Input

  There are a lot of cases. For each case, the first line contains two integers N and M. The follow line contains the M integers, and all of them are different from each other. 0<N<2^31,0<M<=10, and the M integer are non-negative and won’t exceed 20.

Output

  For each case, output the number.

Sample Input

12 2 2 3

Sample Output

7

http://acm.hdu.edu.cn/showproblem.php?pid=1796

題意:

給你n和m,m代表有m個除數。下面一行為給出的m集合。現在要你求m集合中的每一個數能被n整除的個數。

思路一:(dfs)

利用容斥定理,先找出1...m內每一個被n整除的個數,再減去1...m集合中能被兩個數同時除的個數,也就是找他兩個的最小公倍數。然後再加上1...m集合中能被三個數同時除的個數,然後減去1...m集合中能被四個數同時除的個數。以此類推,可以用dfs。注意m集合中不能為0。要特別判斷一下。

程式碼:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
long long a[30];
long long sum1;

int m,cnt,n;

long long gcd(long long p, long long b)
{
	return !b ? p : gcd( b, p%b);
}

void dfs(int i,long long lcm,int tot)
{
	lcm = a[i] / gcd(a[i], lcm) * lcm;
	
	if(tot % 2 == 0){
		sum1 -= (n-1) / lcm;
	}
	else
		sum1 += (n-1) / lcm;
	for(int j = i+1; j < cnt; j++) {
		dfs(j, lcm, tot+1);
	}
}
int main()
{
	while(cin >> n >> m) {
	
		sum1 = 0;
		cnt = 1;
		long long t; 
		for(int i = 1; i <= m; i++) {
			
			cin >> t;
			if(t != 0) {
				a[cnt++] = t;
				
			}
				
		}
		for(int i = 1; i < cnt; i++) {
			dfs(i, a[i], 1);
		}		
		cout << sum1 << endl;
			
	}
	 	
	
	return 0;
 } 

思路二(二進位制列舉)

參考部落格:https://blog.csdn.net/zhhx2001/article/details/51848789

 

三種常用的位運算子:與&、或|、異或^;

與運算:兩者都為 1時,結果即為1,否則為0。--有0出0
或運算:兩者都為 00時,結果即為0,否則為1。--有1出1
異或運算:是兩者同為 0 或1 時,結果即為0,否則為1。相同出1 ,相異出0;
位運算子中有兩種操作,左移<<和右移>>。
對於A << B,表示把A轉化為二進位制後向左移動B位(在末尾新增B個0)。

對於A >> B,表示把A轉化為二進位制後向右移動B位(刪除末尾的B位)。

如2<<2 就是二進位制的10左移2位:二進位制的1000 轉為10進製為8;
 

如果二進位制存在兩個1,也就是取兩個的最小公倍數,如果存在3個1,也就是取三個的最小公倍數。

程式碼:

#include<bits/stdc++.h>
using namespace std;
int s[20];
int gcd(int a, int b)
{
	return b ? gcd( b, a%b) : a;
}
int main()
{
	long long n, m;
	while(cin >> n >> m){
		int Case = 1;
		while(m--) {
			int t;
			cin >> t;
			if(t != 0)
				s[Case++] = t;
		}
		Case--;
		int ans = 0; 
		for(int i = 1; i < (1 << Case); i++) {
			int k = 1; 
			int x = 0;
			for(int j = 0; j < Case; j++) {
				if(i & (1 << j)) {
					k = s[j+1] / gcd( s[j+1], k) * k;
					x++;
				}
			}
			if(x & 1) 
				ans += (n-1) / k;
			else
				ans -= (n-1) / k;
		}
		cout << ans << endl;
	}
	
	return 0;
}