//折半枚举(双向搜索)
/*
思想:枚举集合过大,拆成两个集合分别枚举
简单的说就是有个问题需要四重循环,拆成两个二重循环
例子:
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
今日推荐
周排行