显然如果没有树的限制,我们优先选 \(0\),然后选 \(1\)。
如果有了树的限制,我们考虑下面这么一种贪心方法:假设当前能够选的点的集合为 \(S\)(初始时 \(S\) 只包含根),然后选出 \(S\) 中优先级最大的点 \(u\)(\(0\) 的优先级大于 \(1\) 的优先级)放在序列末尾,然后把 \(u\) 从 \(S\) 中删除,并且把 \(u\) 的儿子都塞进 \(S\) 里面,再重复上述过程直至 \(S\) 为空为止。
这个贪心方法看起来很对,但可能会出现下面这种情况:
如图,我们选完根节点 \(1\) 后,\(S\) 中包含的是节点 \(2\) 和节点 \(3\),显然这两个优先级相同,那我们是不是随便选一个就好了呢?显然不是,因为如果选节点 \(3\) 之后能得到两个 \(0\),但如果选节点 \(2\) 之后只能得到一个 \(0\),所以显然选节点 \(3\) 更优。
这说明当 \(S\) 中两个点优先级相同时,应该还需要有第二关键字作比较。但你发现这个第二关键字不太好处理,下面两组数据应该能 hack 掉大部分:
-
最优选择:\(\{1,3,5,6,2,4,7,8,9\}\),得到序列:\(\{0,1,0,0,1,0,1,1,1\}\)。
-
最优选择:\(\{1,2,4,7,8,9,10,\cdots,114514,3,5,6\}\),得到序列:\(\{0,1,0,1,0,0,0,\cdots,0,1,0,0\}\)。
我们考虑换一种方法:我们先把所有点按优先级排序。
我们先取出最优的那个,设为 \(u\)。若 \(u\) 为根,那么直接选即可;若 \(u\) 不为根,那么设 \(u\) 的父亲为 \(f\),那么当 \(f\) 被选之后是否一定马上选 \(u\) 呢?如果 \(f\) 的其他儿子的优先级都比 \(u\) 小那么肯定是的,但如果有多个儿子和 \(u\) 优先级相同呢?(就和我们一开始说的情况一样)答案也是肯定的,因为即使 \(f\) 有多个和 \(u\) 同样优先级的儿子,由于 \(u\) 是堆里面最优的,所以这些儿子的优先级肯定也都是最优的,于是要选肯定是把这些儿子一起全部选完,所以这些儿子之间没有先后顺序之分,所以你钦定让 \(f\) 被选之后马上选 \(u\) 是没有问题的。
注意这个结论只针对最优的那个点 \(u\),对其他点并不适用。但既然我们已经知道了这个结论,不妨直接把最优点 \(u\) 的贡献给处理掉:若 \(u\) 为根则直接选它,若 \(u\) 不为根则 \(f\) 被选之后马上选 \(u\) ,不妨直接让 \(u\) 和 \(f\) 合并。然后再找到此时的最优点重复上述步骤。这个过程需要用可删堆/set来维护。注意由于一个点可能是由多个点合并得到的,此时的优先级是以 \(\dfrac{0\text{的个数}}{1\text{的个数}}\) 作为比较标准,这个可以用全序来证明。
代码如下:
#include<bits/stdc++.h>
#define N 200010
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,rt[N],fa[N];
int nn0[N],nn1[N];
bool del[N];
ll ans;
struct cmp//重载set中int比较的方法
{
bool operator()(const int &a,const int &b) const //这里末尾一定要有const
{
if(1ll*nn1[a]*nn0[b]==1ll*nn0[a]*nn1[b]) return a<b;
return 1ll*nn1[a]*nn0[b]<1ll*nn0[a]*nn1[b];
}
};
set<int,cmp>s;
int find(int x)
{
return x==rt[x]?x:(rt[x]=find(rt[x]));
}
int main()
{
del[0]=1;
n=read();
for(int i=1;i<=n;i++) rt[i]=i;
for(int i=2;i<=n;i++) fa[i]=read();
for(int i=1;i<=n;i++) (read()?nn1[i]:nn0[i])++;
for(int i=1;i<=n;i++) s.insert(i);
int d0=0,d1=0;
while(!s.empty())
{
int u=(*s.begin());
s.erase(s.begin());
int f=find(fa[u]);
if(del[f])
{
del[u]=1;
ans+=1ll*d1*nn0[u];
d0+=nn0[u],d1+=nn1[u];
continue;
}
s.erase(f);
rt[u]=f;
ans+=1ll*nn1[f]*nn0[u];
nn0[f]+=nn0[u],nn1[f]+=nn1[u];
s.insert(f);
}
printf("%lld\n",ans);
return 0;
}
标签:ch,优先级,int,Tree,01,全序,最优,节点
From: https://www.cnblogs.com/ez-lcw/p/16837055.html