• 【状态压缩 meet in middle】poj3139Balancing 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[]$数组是要先排序的,否则next_permutation无法遍历全部情况。

     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 }

    真是很奇妙啊……

    END

  • 相关阅读:
    两年工作感想
    ASP常用的38个内置函数
    asp汉字转换成汉语拼音
    js高级表格排序
    使用XmlHttpRequest对象调用Web Services 服务
    75个最佳Web设计资源
    C# FTP操作类
    存储过程操作类
    Windows Mobile 5.0 SDK 下载地址
    链表C#实现
  • 原文地址:https://www.cnblogs.com/antiquality/p/9277661.html
Copyright © 2020-2023  润新知