Luogu3067 平衡的奶牛群 Meet in the middle

题意:给出$N$个范围在$[1,10^8]$内的整数,问有多少种取数方案使得取出来的数能够分成两个和相等的集合。$N \leq 20$



发现爆搜是$O(3^N)$的,所以考虑双向搜索。

先把前$3^\frac{N}{2}$搜完,然后每一次搜出后$3^\frac{N}{2}$的时候,枚举前面的$2^\frac{N}{2}$,每一个对应一下看有没有和为$0$的方案即可。复杂度为$O(6^\frac{N}{2})$,虽然不开O2过不去qwq

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3
 4 inline int read(){
 5     int a = 0;
 6     char c = getchar();
 7     while(!isdigit(c))
 8     c = getchar();
 9     while(isdigit(c)){
10     a = (a << 3) + (a << 1) + (c ^ ‘0‘);
11     c = getchar();
12     }
13     return a;
14 }
15
16 struct HashTable{
17 #define MOD 103
18     struct node{
19         int num;
20         node* nxt;
21     }*begin[MOD] , *last[MOD];
22     void insert(int num){
23         int t = num % MOD;
24         if(t < 0)
25             t += MOD;
26         if(last[t] == NULL){
27             begin[t] = new node;
28             begin[t]->num = num;
29             begin[t]->nxt = NULL;
30             last[t] = begin[t];
31         }
32         else{
33             node* now = new node;
34             now->num = num;
35             now->nxt = NULL;
36             last[t]->nxt = now;
37             last[t] = now;
38         }
39     }
40
41     bool count(int num){
42         int t = num % MOD;
43         if(t < 0)
44             t += MOD;
45         for(node* i = begin[t] ; i != NULL ; i = i->nxt)
46             if(i->num == num)
47                 return 1;
48         return 0;
49     }
50 }zt[1 << 10];
51 int M[21] , N , ans;
52 bool is[1 << 10][1 << 10];
53
54 void init(int now , int end , int cnt , int sum){
55     if(now > end){
56         zt[cnt].insert(sum);
57         return;
58     }
59     init(now + 1 , end , cnt , sum);
60     init(now + 1 , end , cnt | (1 << now) , sum + M[now]);
61     init(now + 1 , end , cnt | (1 << now) , sum - M[now]);
62 }
63
64 void getAns(int now , int end , int cnt , int sum){
65     if(now > end){
66     for(int i = 0 ; i < 1 << (N >> 1) ; i++)
67         if(!is[cnt][i] && (zt[i].count(sum) || zt[i].count(-sum))){
68             is[cnt][i] = 1;
69             ans++;
70         }
71     return;
72     }
73     getAns(now + 1 , end , cnt , sum);
74     getAns(now + 1 , end , cnt | (1 << now - (N >> 1)) , sum + M[now]);
75     getAns(now + 1 , end , cnt | (1 << now - (N >> 1)) , sum - M[now]);
76 }
77
78 int main(){
79     N = read();
80     for(int i = 0 ; i < N ; i++)
81     M[i] = read();
82     init(0 , (N >> 1) - 1 , 0 , 0);
83     getAns(N >> 1 , N - 1 , 0 , 0);
84     cout << ans - 1;
85     return 0;
86 }

再放一个复杂度似乎不对但是很快的方法

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3
 4 struct node{
 5     int zt , sum;
 6 }num1[60010] , num2[60010];
 7 int N , cnt1 , cnt2 , M[21];
 8 bool vis[2000010];
 9
10 void dfs(node* num , int& cnt , int now , int end , int sum , int zt){
11     if(now > end){
12         num[++cnt].sum = sum;
13         num[cnt].zt = zt;
14         return;
15     }
16     dfs(num , cnt , now + 1 , end , sum , zt);
17     dfs(num , cnt , now + 1 , end , sum + M[now] , zt | (1 << now));
18     dfs(num , cnt , now + 1 , end , sum - M[now] , zt | (1 << now));
19 }
20
21 bool cmp(node a , node b){
22     return a.sum < b.sum;
23 }
24
25 bool operator == (node a , node b){
26     return a.zt == b.zt && a.sum == b.sum;
27 }
28
29 int main(){
30     cin >> N;
31     for(int i = 0 ; i < N ; i++)
32         cin >> M[i];
33     dfs(num1 , cnt1 , 0 , (N - 2) >> 1 , 0 , 0);
34     dfs(num2 , cnt2 , N >> 1 , N - 1 , 0 , 0);
35     sort(num1 + 1 , num1 + cnt1 + 1 , cmp);
36     sort(num2 + 1 , num2 + cnt2 + 1 , cmp);
37     cnt1 = unique(num1 + 1 , num1 + cnt1 + 1) - num1 - 1;
38     cnt2 = unique(num2 + 1 , num2 + cnt2 + 1) - num2 - 1;
39     int p1 = cnt2 , p2 = cnt2;
40     for(int i = 1 ; i <= cnt1 ; i++){
41         while(p1 && num1[i].sum + num2[p1].sum > 0)
42             p1--;
43         p2 = p1;
44         while(p2 && num1[i].sum + num2[p2].sum >= 0)
45             p2--;
46         while(++p2 <= p1)
47             vis[num1[i].zt | num2[p2].zt] = 1;
48     }
49     int ans = 0;
50     for(int i = 1 ; i < 1 << N ; i++)
51         ans += vis[i];
52     cout << ans;
53     return 0;
54 }

原文地址:https://www.cnblogs.com/Itst/p/9813382.html

时间: 2024-10-04 21:09:19

Luogu3067 平衡的奶牛群 Meet in the middle的相关文章

折半搜索+状态压缩【P3067】 [USACO12OPEN]平衡的奶牛群Balanced Cow S…

Description 给n个数,从中任意选出一些数,使这些数能分成和相等的两组. 求有多少种选数的方案. Input 第\(1\)行:一个整数\(N\) 第\(2\)到\(N+1\)行,包含一个整数\(m_i\) Output 一行:平衡的集合的个数. 看到题的一瞬间数据范围? \(N \leq 20?\)状压! 明显直接做过不去,选择折半搜索. 折半搜索的话会有三种情况 一.选择当前位置 二.选择当前位置,给第一组. 三.选择当前位置,给第二组. 然后直接跑折半搜索+状压即可. 存储类似链式

子集(状态压缩)(meet in the middle)

子集 [问题描述] R 君得到了?个集合,???共有 n 个正整数. R 君对这个集合很感兴趣. R 君通过努?钻研,发现了这个集合?共有 2n 个子集. 现在 R 君又对这个集合的?集很感兴趣. 定义?个集合的权值是这个集合内所有数字的和的话. 那么 R 君想问问你,这个集合的权值第 K小子集是多?. ps. 涉及到较少数字的 long long 输?输出,建议使用 cin/cout. [输入格式] 第??两个正整数 n,k. 接下来一行 n 个正整数,表?集合内元素. [输出格式] 输出?个

SPOJ4580 ABCDEF(meet in the middle)

题意 题目链接 Sol 发现abcdef是互不相关的 那么meet in the middle一下.先算出abc的,再算def的 注意d = 0的时候不合法(害我wa了两发..) #include<bits/stdc++.h> #define LL long long using namespace std; const int MAXN = 101, SS = 2e6 + 10; map<LL, LL> mp; int N; LL a[MAXN], ans; int a1[SS]

codevs1735 方程的解数(meet in the middle)

题意 题目链接 Sol 把前一半放在左边,后一半放在右边 meet in the middle一波 统计答案的时候开始想的是hash,然而MLE了两个点 实际上只要排序之后双指针扫一遍就行了 #include<bits/stdc++.h> using namespace std; const int MAXN = 7, MAX = 1e7 + 10; int K[MAXN], P[MAXN], N, M, ans; int a1[MAX], c1, a2[MAX], c2, cnt[MAX];

[CSP-S模拟测试]:毛一琛(meet in the middle)

题目描述 历史学考后,$MYC$和$ztr$对答案,发现选择题他们没有一道选的是一样的.最后他们都考了个$C$.现在问题来了,假设他们五五开,分数恰好一样(问答题分数也恰好一样,只考虑选择题).已知考题是$N$道选择题(第$i$题分数为$M(i)$).问$ztr$和$MYC$做对的题的并有多少种可能?众所周知,历史学考选择题有$25$题,但是$MYC$为了给你降低难度,$n$不超过$20$. 一句话题意:有多少个非空子集,能划分成和相等的两份. 原题见:$USACO\ 2012\ OPEN\ G

「10.13」毛一琛(meet in the middle)&#183;毛二琛(DP)&#183;毛三琛(二分+随机化???)

A. 毛一琛 考虑到直接枚举的话时间复杂度很高,我们运用$meet\ in\ the\ middle$的思想 一般这种思想看似主要用在搜索这类算法中 发现直接枚举时间复杂度过高考虑枚举一半另一半通过其他算法统计,保证两边互不影响 今天的题我们考虑枚举先枚举左半部分,然后每个物品有三种取值情况 选入A集合,选入B集合,不选,系数不同 考虑完左半部分再去考虑右半部分,那么我们可以用哈系表先从将左半部分的答案统计出来 然后右半部分查询他的相反数注意去重 也可以用将两边状态都用结构体存下来 注意去重 思

折半搜索(meet in the middle)

折半搜索(meet in the middle) ? 我们经常会遇见一些暴力枚举的题目,但是由于时间复杂度太过庞大不得不放弃. ? 由于子树分支是指数性增长,所以我们考虑将其折半优化; 前言 ? 这个知识点曾经在模拟赛中出现过,所以这里稍微提一下; ? 讲的很浅显,但是不要D讲者; 入门 ? dfs搜索树是指数性增长,如果将指数减少一半,就将会有量的飞跃,所以在遇见暴力枚举太大时,我们可以考虑这种算法; ? 总体思想即,dfs搜素通常从一个点出发,遍历所有深度,那么我们考虑将深度减半,从两个点出

bzoj 1770 [Usaco2009 Nov]lights 燈 meet in the middle

题面 题目传送门 解法 题解里都是高斯消元然后dfs,蒟蒻表示不会 直接分两半dfs即可 时间复杂度:\(O(2^{\frac{n}{2}})\) 代码 #include <bits/stdc++.h> #define LL long long #define N 110 using namespace std; template <typename node> void chkmax(node &x, node y) {x = max(x, y);} template &

【gym102222K】Vertex Covers(高维前缀和,meet in the middle)

题意:给定一张n点m边的图,点带点权,定义点覆盖的权值为点权之积,问所有点覆盖的权值之和膜q n<=36, 1<=a[i]<=1e9,1e8<=q<=1e9 思路:n<=36,考虑middle in the middle分成两个点数接近的点集L和R 对于L,枚举其子集S,判断S能否覆盖所有L内部的边,预处理出所有合法的S的超集的贡献 对于R,枚举其子集T,判断T能否覆盖所有R内部的边,如果可以则可以推出L,R之间在确定R中选T的前提下左边至少需要选点集T’,答案即为T的