【状态压缩 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$。千万级别的状态数……还是吃不太消。
#include<bits/stdc++.h> int a[],s[],c[];
int mp[][],f[],bel;
bool vis[],tk[];
int scenario;
long long ans; void get(int x, int done, bool opt)
{
if (done==){
register int statu = , tt;
c[] = ;
for (int i=; i<x; i++)
if (tk[i]) statu += <<(i-), c[++c[]] = a[s[i]];
for (int i=; i<=; i++)
for (int j=; j<=; j++)
if (i!=j)
for (int k=; k<=; k++)
if (i!=k&&j!=k)
for (int l=; l<=; l++)
if (i!=l&&j!=l&&k!=l){
tt = i*c[]+j*c[]+k*c[]+l*c[];
if (!opt)
mp[statu][tt]++;
else f[bel] += mp[-statu][tt];
}
return;
}
if (x > ) return;
tk[x] = , get(x+, done+, opt);
tk[x] = , get(x+, done, opt);
}
void check()
{
memset(mp, , sizeof mp);
s[] = , bel = ;
for (int i=; i<=; i++)
if (vis[i]) s[++s[]] = i, bel += <<(i-);
get(, , );
tk[] = ;
get(, , );
tk[] = ;
}
void dfs(int now, int done, bool opt)
{
if (done==){
if (!opt) check();
else{
int status = ;
for (int i=; i<now; i++)
if (vis[i]) status += <<(i-);
ans += 1ll*f[status]*f[-status];
}
return;
}
if (now > ) return;
vis[now] = , dfs(now+, done+, opt);
vis[now] = , dfs(now+, done, opt);
}
int main()
{
while (scanf("%d",&a[])&&a[])
{
memset(f, , sizeof f);
for (int i=; i<=; i++) scanf("%d",&a[i]);
dfs(, , );
ans = ;
dfs(, , );
printf("Case %d: %lld\n",++scenario,ans);
}
return ;
}
高效合并信息
其实上一种做法最关键受限在于选出8个后再选4个。这里每次选4个都是相对独立的操作,而不能共享重复的信息。也就是说,我们其实可以一开始就只考虑选4个,再考虑合并成选8个。
用$f[x]$表示所有权值为$x$的选数方案,于是合并时就可以通过位运算来判断两个状态是否能够合并了。最后统计时也是一样,统计所有选了8个的状态就行了。
第二种相当于是从小信息合并回大信息,不仅时间复杂度优秀,理解和代码复杂度也十分轻松。
注意一下这样子的话,$a[]$数组是要先排序的,否则next_permutation无法遍历全部情况。
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm> std::vector<int> f[];
int bit[],a[],mp[],scenario;
long long ans; bool check(int x)
{
bit[] = ;
for (int i=; i<; i++)
if (x&(<<i))
bit[++bit[]] = a[i+];
return bit[]==;
}
int main()
{
while (scanf("%d",&a[])&&a[])
{
memset(mp, , sizeof mp);
for (int i=; i<=; i++) scanf("%d",&a[i]);
std::sort(a+, a+);
for (int i=; i<=; i++) f[i].clear();
for (int i=; i<=; i++)
if (check(i))
do{
int tt = bit[]*+bit[]*+bit[]*+bit[]*;
for (unsigned int j=; j<f[tt].size(); j++)
if ((i&f[tt][j])==) mp[i|f[tt][j]]++;
f[tt].push_back(i);
}while (std::next_permutation(bit+, bit+));
ans = ;
for (int i=; i<=; i++)
ans += 1ll*mp[i]*mp[^i];
printf("Case %d: %lld\n",++scenario,ans/);
}
return ;
}
真是很奇妙啊……
END
【状态压缩 meet in middle】poj3139Balancing the Scale的更多相关文章
- [poj3904]Sky Code_状态压缩_容斥原理
Sky Code poj-3904 题目大意:给你n个数,问能选出多少满足题意的组数. 注释:如果一个组数满足题意当且仅当这个组中有且只有4个数,且这4个数的最大公约数是1,$1\le n\le 10 ...
- POJ 1198 / HDU 1401 Solitaire (记忆化搜索+meet in middle)
题目大意:给你一个8*8的棋盘,上面有四个棋子,给你一个初始排布,一个目标排布,每次移动,可以把一个棋子移动到一个相邻的空位,或者跨过1个相邻的棋子,在保证棋子移动不超过8次的情况下,问能否把棋盘上的 ...
- POJ 3254. Corn Fields 状态压缩DP (入门级)
Corn Fields Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 9806 Accepted: 5185 Descr ...
- HDU 3605:Escape(最大流+状态压缩)
http://acm.hdu.edu.cn/showproblem.php?pid=3605 题意:有n个人要去到m个星球上,这n个人每个人对m个星球有一个选择,即愿不愿意去,"Y" ...
- [HDU 4336] Card Collector (状态压缩概率dp)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4336 题目大意:有n种卡片,需要吃零食收集,打开零食,出现第i种卡片的概率是p[i],也有可能不出现卡 ...
- HDU 4336 Card Collector (期望DP+状态压缩 或者 状态压缩+容斥)
题意:有N(1<=N<=20)张卡片,每包中含有这些卡片的概率,每包至多一张卡片,可能没有卡片.求需要买多少包才能拿到所以的N张卡片,求次数的期望. 析:期望DP,是很容易看出来的,然后由 ...
- codeforces B - Preparing Olympiad(dfs或者状态压缩枚举)
B. Preparing Olympiad You have n problems. You have estimated the difficulty of the i-th one as inte ...
- NOIP2005过河[DP 状态压缩]
题目描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数 ...
- vijos1426兴奋剂检查(多维费用的背包问题+状态压缩+hash)
背景 北京奥运会开幕了,这是中国人的骄傲和自豪,中国健儿在运动场上已经创造了一个又一个辉煌,super pig也不例外……………… 描述 虽然兴奋剂是奥运会及其他重要比赛的禁药,是禁止服用的.但是运动 ...
随机推荐
- IT兄弟连 JavaWeb教程 Servlet会话跟踪 Cookie路径问题
操作Cookie时,需要注意路径问题: 设置操作:任何路径都可以设置Cookie,但是有时我们也是用设置进行替换Cookie和删除Cookie(maxAge=0)! 替换:只能由完全相同的路径来操作! ...
- python 基础(十三) time模块
日期和时间 一.time模块 import time 时间戳: 时间戳是指格林威治时间1970年1月1日0时0分0秒至现在的秒数 s(秒).ms(毫秒).μs(微秒).ns(纳秒), 其中:1 ...
- ASP.NET经典的、封装好的ADO.NET类包
using System; using System.Collections; using System.Collections.Specialized; using System.Runtime.R ...
- Sonar静态代码扫描环境搭建(Windows10)
一.环境配置: 1.jdk安装及配置 2.MySQL数据库安装----直接调用服务器院端的MySQL数据库,在此基础上创建新的数据库sonar. 数据库的配置如下: 3.sonar官网下载sonar ...
- Gridview基础
gridview是封装好的,直接在设计界面使用,基本不需要写代码 1.绑定数据源 GridView最好与LinQDatasourse配合使用,相匹配绑定数据: 2.外观控制—— 点开有自动套用格式 布 ...
- sql server技巧
--查出数据最新的存储过程select name,modify_date from sys.procedures where modify_date>'2017-05-26 17:21:09.3 ...
- C#入门笔记1
C#是用于创建要运行在.NET CLR上的应用程序的语言之一,从C和C++语言演化而来,是微软专门为使用.NET平台而创建的.优点:就是彻头彻尾为.NET Framework设计语言. C#能编写什么 ...
- I/O————字符流和流的关闭
Reader字符输入流 Writer字符输出流 用于字符的形式读取和写入数据 FileReader与FileWriter分别是Reader与Writer的子类 public class CharScr ...
- 学习php中的mysql()函数
1.mysql_connect(1,2,3):连接数据库服务器语句,一般常用这三个参数(1)数据库服务器地址,(2)用户名,(3)密码:常与die()(或者exit())函数结合使用:die()函数用 ...
- 利用nodejs读取数据库数据生成树结构的json数据
在做后台管理界面的时候,几乎少不了的一个结构就是树形结构,用来做菜单导航: 那么,最希望的就是树结构的所有数据都是读取的数据库,而不是直接代码当中写死,那我们就一步一步来看: 一,建表 字段通常包括: ...