ZOJ-3931-Exact Compression【dp】【bitset】【好题】

来源:互联网 发布:python statement 编辑:程序博客网 时间:2024/06/11 10:55

3931-Exact Compression


Huffman Code is a commonly used optimal prefix code. Here is a simple introduction to Huffman Coding from wikipedia.

The technique works by creating a binary tree of nodes. These can be stored in a regular array, the size of which depends on the number of symbols, S. A node can be either a leaf node or an internal node. Initially, all nodes are leaf nodes, which contain the symbol itself, the weight (frequency of appearance) of the symbol and optionally, a link to a parent node which makes it easy to read the code (in reverse) starting from a leaf node. Internal nodes contain symbol weight, links to two child nodes and the optional link to a parent node. As a common convention, bit ‘0’ represents following the left child and bit ‘1’ represents following the right child. A finished tree has up to n leaf nodes and n-1 internal nodes. A Huffman tree that omits unused symbols produces the most optimal code lengths.

The simplest construction algorithm uses a priority queue where the node with lowest weight is given highest priority:

Create a leaf node for each symbol and add it to the priority queue.
While there is more than one node in the queue:
Remove the two nodes of highest priority (lowest weight) from the queue
Create a new internal node with these two nodes as children and with weight equal to the sum of the two nodes’ weight.
Add the new node to the queue.
The remaining node is the root node and the tree is complete.
For example, one day Edward wanted to send a string “aeaaaageqqqq” to his best friend Min as a gift. There are four symbols ‘a’, ‘e’, ‘g’, ‘q’ and so their weights are 5, 2, 1, 4.

Firstly Edward merged the two symbols ‘e’ and ‘g’ with lowest weights and get a node with weight of 1 + 2 = 3. Then Edward merged two nodes of weight 3 and 4 and get a node with weight 3 + 4 = 7. Finally Edward merged the last two nodes. If we distribute the prefix ‘0’ to the smaller node, Edward can get the code of four symbols ‘0’, ‘101’, ‘100’, ‘11’.

If we know the number of occurrences of each character in some content, can we compress it using Huffman Code and the number of ‘0’s is exactly E? More precisely, let the number of occurrences of the i-th character be Fi and the number of ‘0’s in its huffman code be Ci, we want to know whether there exists a specific huffman code satisfying that the sum of Fi * Ci is equal to E.

Input
There are multiple test cases. The first line of input is an integer T indicates the number of test cases. For each test case:

The first line of each case contains a positive integer S (2 ≤ S ≤ 128), indicates the number of different symbols.

The second line contains exactly S integers Fi (1 ≤ Fi ≤ 1000), indicates the number of occurrence of each symbol.

The third line contains a non-negative integer E (0 ≤ E ≤ 108), indicates the expected number of ‘0’s.

Output
For each case, please output “Yes” if it can be satisfied and “No” in otherwise.

Sample Input
3
2
1 3
2
2
1 3
3
4
5 2 1 4
11
Sample Output
No
Yes
Yes

题目链接:ZOJ 3931

题目大意:给出n个数字,让你组成一颗哈弗曼树(注意两条边0、1可互换)使得权值*该路上有多少个0的和为m,如果可行则输出Yes,否则输出No

题目思路1:
构建一颗哈弗曼树,使得所有符号其编码中的0的数量刚好等于E

不考虑树结点对应的符号和左右子树的区别,只从这棵树的形态上看,这棵树的形态是唯一的。

即:对于每个节点,其左右子树的节点数都是确定的,记为L[i],R[i],不妨令L[i]是两者中较小值
根据哈弗曼树构造时的方式,每条边实际对应的就是编码中的0,1.我们通向左子树的边认为是0,通向右子树的边认为是1

那么对于当前这颗树,其0的数量就相当于sum(L)

想要改变0的数量,可以选择一些结点,交换其左右子树

对于第i个结点的左右子树交换后,左子树中的结点数量就从L[i]变成了R[i],增加了R[i] - L[i],意味着编码中0的数量也增加了这么多

每个节点可以使用(交换)一次,使用后数量增加了 R[i] - L[i]

这不就是背包吗!

问题就变成了选择哪些R[i] - L[i],使得他们的和恰好为E - sum(L)

背包的容量不超过(结点数 * 子树大小之差)大小之差不会超过2000

所以先模拟一下哈弗曼树的构建,得到L[i],R[i]后做个背包就行了

#include<bits/stdc++.h>using namespace std;priority_queue<int, vector<int>, greater<int> >q;int n,m,x,w;int L[1005];int R[1005];//背包容量E - sum(L)map <long long,int> vis;int ans = 0;void solve(){    queue <int> que[2];  //两个队列相当于滚动数组    int pos = 0;    que[pos].push(0);   //存在不改变的情况    for (int i = 0; i < 2 * (n - 1); i++)  //注意i的范围    {        vis.clear();        while(!que[pos].empty())        {            int front = que[pos].front();  //分别取出上一次队列中所有的数字            que[pos].pop();            que[pos ^ 1].push(front);  //可能不做交换            if (front == w)  //如果找到答案,则输出YES            {                ans = 1;                return;            }            int c = R[i] - L[i];  //做交换            if (front + c <= w && !vis[front + c])            {                que[pos ^ 1].push(front + c);                vis[front + c] = 1;            }        }        pos ^= 1;    }}int main(){    int t;    cin >> t;    while(t--)    {        scanf("%d",&n);        ans = 0;        memset(L,0,sizeof(L));        memset(R,0,sizeof(R));        while(!q.empty()) q.pop();        for (int i = 1; i <= n; i++)        {            scanf("%d",&x);            q.push(x);  //将原来的数字以从大到小的顺序存在q这个优先队列中        }        scanf("%d",&m);        int sum_l = 0;        for (int i = 0; i < n - 1; i++)        {            int a = q.top();q.pop();            int b = q.top();q.pop();            q.push(a + b);            L[i] = a;   //建立哈弗曼树,左子树为L,右子树为R,默认左子树较小            R[i] = b;            sum_l += L[i];        }        w = m - sum_l;        solve();        if (ans) cout << "Yes\n";        else cout << "No\n";    }    return 0;}

题目思路2:

利用了bitset,很暴力的做法。刚刚了解的bitset

bitset<12>f, 那么f为12长度的串. f.reset() 将所有位置为0。

例如:5 4 2 1 -> 11

初始串为:
这里写图片描述
f = (f << 1) | (f << 2); //这样的做法是,将f串中所有数字左移1位+ 左移2位。
得到的串为,
这里写图片描述
然后将3放入q中,取出3,4。
根据哈弗曼树的性质我们可以知道,
这里写图片描述

如果选择3这条路为0,那么相当于在原来得到的基础每种情况上+3(因为1和2都多了一个0);如果选择4这条路那么相当于在原来基础上每种情况+4(因为4这多了一个0)

以此类推即可。

感觉bitset长见识了~~~

参考博客:here

以下是代码:

#include<bits/stdc++.h>using namespace std;const int N = 448001;int n,x,m;priority_queue<int, vector<int>, greater<int> >q;bitset<N>f;void solve(){    f.reset();//将f的所有位,置为0    f[0] = 1;    for (int i = 1; i < n;i ++)    {        //取出优先队列中最小的两个数字        int a = q.top(); q.pop();        int b = q.top(); q.pop();        q.push(a + b);        f = (f << a) | (f << b);   //这是一个很神奇的操作,假设f初始为 00001,a为1,b为2,那么改变后f的值为00110    }}int main(){    int t;    cin >> t;    while(t--)    {        scanf("%d",&n);        while(!q.empty()) q.pop();        for (int i = 1; i <= n; i++)        {            scanf("%d",&x);            q.push(x);  //将原来的数字以从大到小的顺序存在q这个优先队列中        }        scanf("%d",&m);        if (m >= N)        {            cout << "No\n";            continue;        }        solve();        if (f[m]) cout << "Yes\n";        else cout << "No\n";    }    return 0;}
0 0
原创粉丝点击