P2036 Perket (状态压缩通过)

Perket 是一种流行的美食。为了做好 Perket,厨师们必须小心选择配料,以便达到更好的口感。你有N种可支配的配料。对于每一种配料,我们知道它们各自的酸度 SS 和甜度 BB。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的甜度为每一种配料的甜度的总和。

众所周知,美食应该口感适中;所以我们希望选取配料,以使得酸度和甜度的绝对差最小。

另外,我们必须添加至少一种配料,因为没有美食是以白水为主要配料的。

输入格式

第一行包括整数 NN,表示可支配的配料数。

接下来 NN 行,每一行为用空格隔开的两个整数,表示每一种配料的酸度和甜度。

输入数据保证,如果我们添加所有配料,总的酸度和甜度都不会超过 10^9109。

输出格式

输出酸度和甜度的最小的绝对差。

输入输出样例

输入 #1
1
3 10

输出 #1
7
输入 #2
4
1 7
2 6
3 8
4 9
输出 #2
1

说明/提示

1\le N\le 101N10。

扫描二维码关注公众号,回复: 10426717 查看本文章


首先这个题目给我们最多十种调料,数字不大,因此可以使用状态压缩去枚举每一种情况。
但是看了很多网上的博客,网上的视频。都没有很好的,面向小白的状态压缩教程。
因此本人决定~~明早python翘了~~深夜发一篇题解。

本人会竭力细致的讲解。So,让我们开始吧!


------------

什么是状态压缩?
状态压缩,可以把复杂的状态压缩到一个简单的数来保存。
是不是和本题看起来一点关系都没有?

别着急!

在讲解状态压缩之前,我们要知道二进制这个神奇的东西,以及知道如何将十进制转化为二进制。

下面是十进制转化成二进制的代码(短除法)


------------

```cpp
void dectobin(int n)
{
    int result = 0, k = 1, i, temp;
    temp = n;
    while (temp)
    {
        i = temp % 2;
        result = k * i + result;
        k = k * 10;
        temp = temp / 2;
    }
    printf("%d\n", resu
```

------------

某同学:哦,我的天啊!我知道十进制转化为二进制又有什么用啊?对于本题没有一点帮助啊!

在计算机世界中 0 代表 伪 ,1 代表 真。

那么是不是我们可以用1代表这个调料被选用了,0代表它没有被选用。

注意!注意!

因此假如有四份调料ABCD,某个厨子就让我选ABC三种。

那么用1代表选,0代表不选

是不是这种情况我们可以改写成:

选,选,选,不选

1,1,1, 0


然后啊我们看啊,这个逗号好像完全不影响啊

我们再把逗号删掉,是不是就是1110(这是不是代表这种状态了)

这个1110如果以二进制的角度看,是不是就**唯一对应**一个数了呢

也就是8+4+2=14这个数

~~好了恭喜你已经学会一半的状态压缩了~~

------------
我们现在知道了可以用二进制去表示所有调料是否使用的所有状态

加深印象再举几个例子:还是ABCD

我选了D,ABC不选就是0001

ABCD全选就是1111

ABCD都不选就是0000

有些同志就要说了:啊~,你这个东西啊,我知道用二进制代表状态有什么用呢?难道手撸所有的状态???打表???

NO!

对于这个题目来说一共有2*2*2……*2总共1024种状态

某同志:所以,二进制转化成十进制后对应的最大的数是1024???

其实不然,由上面的例子可以看出

十个调料如果全部要选的话:

是1111111111(十个1)

是2^11 - 1 也就是2047这么大

最多全选,最少选一种(题目要求,所以不是0)

也就是说111111111 ->->->0000000001

当然:0000000010,也满足只选一种

------------
综上所述,我们可以用2047去代表1111111111,然后不停的让自减1,达到枚举全部状态的目的

明白了这些我们再说另外一个很重要的东西 **位运算**

在这里我们简单的说几个下面出现了的,其余的可以去百度


------------


**& 与 运算符** 作用:两位同时为“1”,结果才为“1”,否则为0

运算规则:0&0=0;   0&1=0;    1&0=0;     1&1=1;

**左移运算符(<<)**
将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
例:a = a << 2 将a的二进制位左移2位,右补0,
左移1位后a = a * 2; 
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。

**右移运算符(>>)**
将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
操作数每右移一位,相当于该数除以2。
例如:a = a >> 2 将a的二进制位右移2位,
左补0 or 补1 得看被移数是正还是负


------------
然后我们就发现了一个是原来判断一个数二进制某位是不是1只要来一个按位 & 运算符就OK了啊


```cpp
if (i & (1 << (j - 1)))
```
也就是这句代码

左移运算符:

1<<1 二进制下 :10

1<<2 二进制下: 100

因为二进制的10可以判断1110(随便举得一个二进制数做例子)
的从右往左数的第二位是不是1


二进制的100可以判断11001(同上)
的从右往左数的第三位是不是1

所以我们只要来一个循环不断产生

1,10,100,1000……

这样就可以知道到底是哪个调料被选用了

### 枚举状态 + 扫描情况 + 努力学习写出的c++代码 ==AC!

最后AC代码如下:

细心的同志会发现我开了一个 f[2048]但是完全没有使用啊哈哈哈哈

------------

```cpp
#include <iostream>
#include <cmath>
using namespace std;

int f[2048];
struct
{
int s; //酸度
int t; //甜度
} shicai[18];
int main()
{
int N;
cin >> N;
for (int i = 1; i <= N; i++)
cin >> shicai[i].s >> shicai[i].t;
int c = 9999999; //甜度差,先设置为无限大
for (int i = (1 << N) - 1; i >= 1; i--)
{
int t_t = 0;
int s_t = 1; //s_t千万不要设置为0
for (int j = 1; j <= N; j++) //开始状态压缩,扫一扫
{
if (i & (1 << (j - 1))) //现在对比的是第j位调料
{
t_t += shicai[j].t;
s_t *= shicai[j].s;
}
}
c = min(c, abs(t_t - s_t));
}
cout << c << endl;
return 0;
}
```

猜你喜欢

转载自www.cnblogs.com/leader-one/p/12624159.html
今日推荐