Problem
给一棵有根树的N(
≤105
≤
10
5
)个结点编码一个01串si,要求满足条件:
1. 如果u是v的祖先,那么su必须是sv的前缀
2. 如果u不是v的祖先或后代,那么su不允许是sv的前缀
注意:允许两个点的编码相同。求所有结点编码长度之和的最小值。
Solution
心路历程
刚做时并未发现这题可做。。。部分分都没心情时间打。。。
条件转化
言归正传,讲讲它的正解。
首先,第二个条件其实可以转化为令每个点所有儿子的编号不互为前缀。因为可以设u、v在a的子树内,a的两个儿子b、c分别为u、v的祖先;我们又要满足su不为sv的前缀。那么如果su、sv互为前缀,则sb、sv也互为前缀,进而sb、sc互为前缀。
所以说,对于节点x,我们要使它的所有儿子y满足sx为sy的前缀且sy之间两两不互为前缀。
转换模型
很(bu)容(ke)易(neng)想到用哈夫曼树。对于每个x,我们都将它的所有儿子进行一次哈夫曼编码。设有如下图所示的一棵树:
这棵树的N=4,点1是点2、3、4的父亲。那么我们对于点2、3、4进行一次Huffman,得到下图:
点5、6是我们新建的虚点,边权代表这条边的编码。由于对n个点做Huffman生成的是一棵2n-1个节点、n个叶子结点的树,所以对于x的儿子(这里x=1,它的儿子有点2、3、4),它们都会成为这棵哈夫曼树的叶子,故不可能互为前缀。
同时,囿于此题的编码可以相同,所以我们在对x的儿子做Huffman时,若x仅剩一个儿子了(图中即为合并成点6了),我们就从x向此儿子连一条无编码的边,表示此儿子与x的编码相同。当然,虚点是没有编码的。
点权问题
那么我们再想想做Huffman时那些点的点权。
不难发现,我们选取点权最小的两个点a、b合并,其实就相当于将以a、b为根的子树整体下移一层,即使它们的深度整体+1。这样会使它们的编码长度+1(a那棵子树多了个0编码,b那棵子树多了个1编码)。而编码长度和就会增加size[a]+size[b],这就相当于做Huffman时每次合并a、b会使WPL+=w[a]+w[b]一样。
于是,点权即为子树的size大小。
计算答案
那么,答案的话,我们就dfs一遍,一路将编码长度求个前缀和(譬如fat[y]=x,设len表示该点的编码长度,则遍历到y时使len[y]+=len[x]),然后累计即可。
时间复杂度
时间复杂度: O(nlog2n) O ( n l o g 2 n ) (用堆做Huffman)。
Code
#include <cstdio>
#include <set>
#define fs for(i=last[x];i;i=next[i])
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=2e5+1;
int i,n,p,f,tov[N],next[N],last[N],sz[N],len[N];
ll ans;
inline void link(int x,int y)
{
tov[i]=y;
next[i]=last[x];
last[x]=i;
}
void scan()
{
scanf("%d",&n);p=n;
fo(i,1,n-1)scanf("%d",&f),link(f,i+1);
}
void dfs(int x)
{
sz[x]=1;
int i;
fs dfs(tov[i]),sz[x]+=sz[tov[i]];
}
struct myComp
{
inline bool operator()(const int &a,const int &b){return sz[a]<sz[b];}
};
multiset<int,myComp>s;
multiset<int,myComp>::iterator it;
int a,b,l[N],r[N];
void code(int x)
{
int i;
fs s.insert(tov[i]);
while(s.size()>1)
{
it=s.begin();a=*it;s.erase(it);
it=s.begin();b=*it;s.erase(it);
sz[++p]=sz[a]+sz[b];
len[l[p]=a]=len[r[p]=b]=1;
s.insert(p);
}
if(!s.empty())
{
it=s.begin();
l[x]=*it;
s.erase(it);
}
fs code(tov[i]);
}
void calc(int x)
{
if(x<=n)ans+=1ll*len[x];
if(l[x])len[l[x]]+=len[x],calc(l[x]);
if(r[x])len[r[x]]+=len[x],calc(r[x]);
}
int main()
{
scan();
dfs(1);
code(1);
calc(1);
printf("%lld",ans);
}