状态压缩 meet in middlepoj3139Balancing the Scale

Posted antiquality

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了状态压缩 meet in middlepoj3139Balancing the Scale相关的知识,希望对你有一定的参考价值。

数组溢出真是可怕的事情

Description

You are given a strange scale (see the figure below), and you are wondering how to balance this scale. After several attempts, you have discovered the way to balance it — you need to put different numbers on different squares while satisfying the following two equations:

x1 * 4 + x2 * 3 + x3 * 2 + x4 = x5 + x6 * 2 + x7 * 3 + x8 * 4
y1 * 4 + y2 * 3 + y3 * 2 + y4 = y5 + y6 * 2 + y7 * 3 + y8 * 4

How many ways can you balance this strange scale with the given numbers?

技术分享图片

Input

There are multiple test cases in the input file. Each test case consists of 16 distinct numbers in the range [1, 1024] on one separate line. You are allowed to use each number only once.

A line with one single integer 0 indicates the end of input and should not be processed by your program.

Output

For each test case, if it is possible to balance the scale in question, output one number, the number of different ways to balance this scale, in the format as indicated in the sample output. Rotations and reversals of the same arrangement should be counted only once.

Sample Input

87 33 98 83 67 97 44 72 91 78 46 49 64 59 85 88
0

Sample Output

Case 1: 15227223

题目大意

有16个重量互不相同的砝码,问有多少种不同的方案能够使这16个砝码分为两组分别平衡。两个方案不相同当且仅当它们不能够互相旋转或翻转得到。

题目分析

显然是道meet in middle,但是把什么状态折半呢?

$C_{16}^{8},C_{8}^{4}$

分两次折半搜索,先从16个里取8个,再从选出的8个里取4个。

先用二进制状态压缩,再用$f[i]$表示$i$这个取了8个的状态有多少种合法方案。因为最后的统计是独立的,所以对于总共16个数拆成的状态$i$和状态$j$来说,它们对答案的贡献是$f[i]*f[j]$。

再来考虑如何计算$f[x]$。因为$x$这个状态是选了8个砝码的状态,最普通的计算当然就是$8!$地枚举有多少满足条件。毋庸置疑这里也可以用meet in middle来优化,具体就是用$mp[i][j]$表示$i$这个状态下权值为$j$的方案数。因为8选4之后的4个有$4!$的排列情况,每一种情况的权值都不一样。之后的操作就是比较经典的meet in middle模型了。算出来后再将答案贡献至$f[]$即可。

这是一个正确的算法,但是所要计算的状态数达到了$2*C_{16}^{8}*C_{7}^{4}*4!=21621600$。千万级别的状态数……还是吃不太消。

 

 1 #include<bits/stdc++.h>
 2 
 3 int a[23],s[23],c[13];
 4 int mp[260][10305],f[66003],bel;
 5 bool vis[23],tk[13];
 6 int scenario;
 7 long long ans;
 8 
 9 void get(int x, int done, bool opt)
10 {
11     if (done==4){
12         register int statu = 0, tt;
13         c[0] = 0;
14         for (int i=1; i<x; i++)
15             if (tk[i]) statu += 1<<(i-1), c[++c[0]] = a[s[i]];
16         for (int i=1; i<=4; i++)
17             for (int j=1; j<=4; j++)
18                 if (i!=j)
19                     for (int k=1; k<=4; k++)
20                         if (i!=k&&j!=k)
21                             for (int l=1; l<=4; l++)
22                                 if (i!=l&&j!=l&&k!=l){
23                                     tt = i*c[1]+j*c[2]+k*c[3]+l*c[4];
24                                     if (!opt)
25                                         mp[statu][tt]++;
26                                     else f[bel] += mp[255-statu][tt];
27                                 }
28         return;
29     }
30     if (x > 8) return;
31     tk[x] = 1, get(x+1, done+1, opt);
32     tk[x] = 0, get(x+1, done, opt);
33 }
34 void check()
35 {
36     memset(mp, 0, sizeof mp);
37     s[0] = 0, bel = 0;
38     for (int i=1; i<=16; i++)
39         if (vis[i]) s[++s[0]] = i, bel += 1<<(i-1);
40     get(2, 0, 0);
41     tk[1] = 1;
42     get(2, 1, 1);
43     tk[1] = 0;
44 }
45 void dfs(int now, int done, bool opt)
46 {
47     if (done==8){
48         if (!opt) check();
49         else{
50             int status = 0;
51             for (int i=1; i<now; i++)
52                 if (vis[i]) status += 1<<(i-1);
53             ans += 1ll*f[status]*f[65535-status];
54         }
55         return;
56     }
57     if (now > 16) return;
58     vis[now] = 1, dfs(now+1, done+1, opt);
59     vis[now] = 0, dfs(now+1, done, opt);
60 }
61 int main()
62 {
63     while (scanf("%d",&a[1])&&a[1])
64     {
65         memset(f, 0, sizeof f);
66         for (int i=2; i<=16; i++) scanf("%d",&a[i]);
67         dfs(1, 0, 0);
68         ans = 0;
69         dfs(2, 0, 1);
70         printf("Case %d: %lld
",++scenario,ans);
71     }
72     return 0;
73 }

 

 

 

高效合并信息

其实上一种做法最关键受限在于选出8个后再选4个。这里每次选4个都是相对独立的操作,而不能共享重复的信息。也就是说,我们其实可以一开始就只考虑选4个,再考虑合并成选8个。

用$f[x]$表示所有权值为$x$的选数方案,于是合并时就可以通过位运算来判断两个状态是否能够合并了。最后统计时也是一样,统计所有选了8个的状态就行了。

第二种相当于是从小信息合并回大信息,不仅时间复杂度优秀,理解和代码复杂度也十分轻松。

注意一下这样子的话,$a[]$数组是要先排序的,因为这种做法要求合并信息时的有序。

 1 #include<vector>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 
 6 std::vector<int> f[10251];
 7 int bit[20],a[23],mp[70003],scenario;
 8 long long ans;
 9 
10 bool check(int x)
11 {
12     bit[0] = 0;
13     for (int i=0; i<16; i++)
14         if (x&(1<<i))
15             bit[++bit[0]] = a[i+1];
16     return bit[0]==4;
17 }
18 int main()
19 {
20     while (scanf("%d",&a[1])&&a[1])
21     {
22         memset(mp, 0, sizeof mp);
23         for (int i=2; i<=16; i++) scanf("%d",&a[i]);
24         std::sort(a+1, a+17);
25         for (int i=0; i<=10240; i++) f[i].clear();
26         for (int i=0; i<=65535; i++)
27             if (check(i))
28                 do{
29                     int tt = bit[1]*1+bit[2]*2+bit[3]*3+bit[4]*4;
30                     for (unsigned int j=0; j<f[tt].size(); j++)
31                         if ((i&f[tt][j])==0) mp[i|f[tt][j]]++;
32                     f[tt].push_back(i);
33                 }while (std::next_permutation(bit+1, bit+5));
34         ans = 0;
35         for (int i=0; i<=65535; i++)
36             ans += 1ll*mp[i]*mp[65535^i];
37         printf("Case %d: %lld
",++scenario,ans/2);
38     }
39     return 0;
40 }

 

真是很奇妙啊……

 

以上是关于状态压缩 meet in middlepoj3139Balancing the Scale的主要内容,如果未能解决你的问题,请参考以下文章

poj 1198 / hdu 1401 Solitaire (记忆化搜索+meet in middle)

meet-in-the-middle 基础算法(优化dfs)

Murano Weekly Meeting 2016.05.31

五月集训(第31天) —— 状态压缩

七月集训(第31天) —— 状态压缩

七月集训(第31天) —— 状态压缩