【暑假集训笔记】折半枚举+超大背包(无解析)+抽屉原理(鸽巢原理)+整数关系的应用

//折半枚举(双向搜索)
/*
思想:枚举集合过大,拆成两个集合分别枚举
简单的说就是有个问题需要四重循环,拆成两个二重循环


例子:
POJ 2785:给定各有n个整数的四个数列A,B,C,D。从每个数列中各取一个数,使四个数的和为0.
当一个数列中有多个相同数字时,把它们作为不同的数字看待。求出这样的组合的个数。
void solve()
{
    //枚举从C和D中取出数字的所有方法
	int k = 0;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
            cd[k] = c[i] + d[j];
			k++;
        }
    }
    sort(cd, cd + k);
 
    ll res = 0;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < n; j++){
			
            int CD = -(a[i] + b[j]);
			
            //取出C和D中和为CD的部分求满足CD值的个数
            res += upper_bound(cd, cd + n * n, CD) - lower_bound(cd, cd + n * n, CD);
        }
    }
    cout<<res<<endl;
}
*/
/*折半枚举用于超大背包问题(放代码,不分析)
题意:有重量和价值分别为wi,vi的n个物品。从这些物品中挑选总重量不超过W的物品,求所有挑选方案中价值总和的最大值。

限制条件:
1 <= n <= 40
1 <= wi, vi <= 10 的15次幂
1 <= W <= 10的15次幂

输入:
n = 4
w = {2, 1, 3, 2}
v = {3, 2, 4, 2}
W = 5

输出:
7(挑选0、1、3号物品)

>>:除以2
<<: 乘以2 1<<n  == 2^n

*/

#include <cstdio>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
const int N = 50;  
const long long INF = 0x3fffffff;  
typedef long long LL;  
int n;  
LL w[N], v[N];  
LL W;  
pair <LL, LL> pi[1 << (N / 2)];  
  
void solve() {  
    int n2 = n / 2;  
    for(int i = 0; i < (1 << n2); i++) {  
        LL sw = 0, sv = 0;  
        for(int j = 0; j < n2; j++) {  
            if((i >> j) & 1) {  
                sw += w[j];  
                sv += v[j];  
            }  
        }  
        pi[i] = make_pair(sw, sv);  
    }  
  
    sort(pi, pi + (1 << n2));  
    int m = 1;  
    for(int i = 1; i < (1 << n2); i++) {  
        if(pi[m-1].second < pi[i].second) {  
            pi[m++] = pi[i];  
        }  
    }  
  
    LL res = 0;  
    for(int i = 0; i < (1 << (n - n2)); i++) {  
        LL sw = 0, sv = 0;  
        for(int j = 0; j < n - n2; j++) {  
            if((i >> j) & 1) {  
                sw += w[n2 + j];  
                sv += v[n2 + j];  
            }  
        }  
        if(sw <= W) {  
            LL tv = (lower_bound(pi, pi + m, make_pair(W - sw, INF)) - 1)->second;  
            res = max(res, sv + tv);  
        }  
    }  
    printf("%lld\n", res);  
}  
  
int main() {  
    while(~scanf("%d%lld", &n, &W)) {  
        for(int i = 0; i < n; i++) {  
            scanf("%lld%lld", &w[i], &v[i]);  
        }  
        solve();  
    }  
    return 0;  
}  
//抽屉原理:把m个东西任意分放进n个空抽屉里(m>n),那么一定有一个抽屉中放进了至少2个东西。
/*
第一抽屉原理: 
原理1: 
把多于n+1个的物体放到n个抽屉里,则至少有一个抽屉里的东西不少于两件。 
证明(反证法):
如果每个抽屉至多只能放进一个物体,那么物体的总数至多是n×1,而不是题设的n+k(k≥1),故不可能。

原理2 : 
把多于mn(m乘n)+1(n不为0)个的物体放到n个抽屉里,则至少有一个抽屉里有不少于(m+1)的物体。 
证明(反证法):
若每个抽屉至多放进m个物体,那么n个抽屉至多放进mn个物体,与题设不符,故不可能。

原理3 : 
把无穷多件物体放入n个抽屉,则至少有一个抽屉里 有无穷个物体。


第二抽屉原理: 
把(mn-1)个物体放入n个抽屉中,其中必有一个抽屉中至多有(m—1)个物体
(例如,将3×5-1=14个物体放入5个抽屉中,则必定有一个抽屉中的物体数少于等于3-1=2)。

构造抽屉:
	简单:多的是物品,少的是抽屉
	复杂:分组法 取余法等
解决问题:
第一步:分析题意。分清什么是“东西”,什么是“抽屉”。
第二步:制造抽屉。根据题目条件和结论,抓住最基本的数量关系,设计和确定解决问题所需的抽屉及其个数,为使用抽屉铺平道路。
第三步:运用抽屉原理。


抽屉原理分类:
	整除关系
	着色问题
	其他
解释整除关系:
		把所有整数按照除以某个自然数m的余数分为m类,叫做m的剩余类或同余类,
		用[0],[1],[2],…,[m-1]表示.每一个类含有无穷多个数,
		例如[1]中含有1,m+1,2m+1,3m+1,….在研究与整除有关的问题时,
		常用剩余类作为抽屉.根据抽屉原理,
		可以证明:任意n+1个自然数中,总有两个自然数的差是n的倍数。
		(证明:n+1个自然数被n整除余数至少有两个相等(抽屉原理),
		不妨记为m=a1*n+b n=a2*n+b,则m-n整除n)。 	
*/
/*
举例:整除问题
有c个孩子,去n个邻居家要糖果,现在已知每个邻居所能给的糖果数ai,
问怎么个要法能保证全部所得的糖果能被c个孩子平分。
问题等价于:
	给出n个数,叫你从中选出任意个数,使得这些数的和是c的倍数。	
分析:
我们考虑前k个邻居的糖果总数,那么这样的和一共有n个,
a1,a1+a2,...,a1+...+an,如果将他们分别除以孩子的个数c,
那么余数只可能在1~c-1之间,换句话说,和有n个,而余数有c-1个,并且从题目中可知c<=n,
那么根据抽屉原理可知,必然有两个和的余数是相同的,
这也就意味着这两个和中包含的公有项的和能被c整除,即为所求答案
*/
//非多实例,提取主代码突出思路
#include <bits/stdc++.h>
using namespace std;

int a[100005];
int boo[100005];
int main()
{
	int n,c;
	int L,R;
	
	cin>>c>>n;
	
	a[0] = 0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i] = (a[i]+a[i-1])%c;//用原数组存余数的原因:数据只用一次就够了
	}
	
	for(int i=1;i<=n;i++){
		if(boo[a[i]] == -1){//如果这个余数没有出现过
			boo[a[i]] = i;//记录第一次出现的下标
		}
		else{//如果出现过这个余数
			L = boo[a[i]];//记录左端点
			R = i;//记录右端点
			break;
		}
	}
	
	if(boo[0]!=-1){//[1,R]满足可以整除//如果曾经出现过和能被整除,就从头输出到能整除的地方
		for(int i = 1;i<=boo[0];i++)
			cout<<i;
		cout<<endl;
	}
	else if(L!=-1){//[L+1,R]区间内满足整除
		for(int i = L+1;i<=R;i++)
			cout<<i;
		cout<<endl;
	}
	else//无论如何也满足不了的
		cout<<"no sweets"<<endl;
	return 0;
}



























猜你喜欢

转载自blog.csdn.net/F_zmmfs/article/details/81330145