1. 程式人生 > >【狀態壓縮 meet in middle】poj3139Balancing the Scale

【狀態壓縮 meet in middle】poj3139Balancing the Scale

per program 十分 滿足 single 狀態壓縮 scenario sort rotation

數組溢出真是可怕的事情

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\n",++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\n",++scenario,ans/2);
38     }
39     return 0;
40 }

真是很奇妙啊……

【狀態壓縮 meet in middle】poj3139Balancing the Scale