OVOO题解:
求包含根节点的第k小连通块的权值,连通块的权值定义为连通块中包含的所有边的权值之和。
使用A* 算法(估价函数为0),维护一个优先队列,优先队列中储存连通块的权值,上一次选的边权和当前连通块周围的可选边集合构成的可并堆,每种状态有如下两种扩展方式:
(1)删除上一次选的边,并选一条当前可选边中权值最小的边。
(2)将上一次选的边的所有出边所构成的可并堆与当前处理状态中的可并堆合并,并从中选出一条权值最小的边,将这条边从可并堆中删除,同时上一条边将不再允许删除。
这种方式保证方案不重复不遗漏,并且每种被扩展完毕的状态,都是一种合法状态,这样保证了算法的时间复杂度。(出队K次即可,每次均为log级),这种扩展方式还使得每次扩展出的状态权值都比大于等于原状态的权值,保证了A*算法的正确性。
为了节省空间,使用可持久化可并堆。
代码:
#include <stdio.h>
#include <queue>
using namespace std;
#define ll long long
int gen[3700010]={0},shu=0;
int l[3700010],r[3700010];
int z[3700010],cd[3700010];
int v[3700010];
int hb1(int x,int y)
{
if(x==0||y==0)
return x+y;
int t;
if(z[x]>z[y])
t=x,x=y,y=t;
r[x]=hb1(r[x],y);
if(cd[l[x]]<cd[r[x]])
t=l[x],l[x]=r[x],r[x]=t;
cd[x]=cd[r[x]]+1;
return x;
}
int hb2(int x,int y)
{
if(x==0||y==0)
return x+y;
int t,rt;
if(z[x]>z[y])
t=x,x=y,y=t;
shu+=1;
rt=shu;
z[rt]=z[x];
l[rt]=l[x];
v[rt]=v[x];
r[rt]=hb2(r[x],y);
if(cd[l[rt]]<cd[r[rt]])
t=l[rt],l[rt]=r[rt],r[rt]=t;
cd[rt]=cd[r[rt]]+1;
return rt;
}
struct SJd
{
ll he;
int wz;
SJd(){}
SJd(ll He,int Wz)
{
he=He;
wz=Wz;
}
};
bool operator<(SJd a,SJd b)
{
return a.he>b.he;
}
int main()
{
cd[0]=-1;
int n,k;
scanf("%d%d",&n,&k);
for(int i=2;i<=n;i++)
{
int f,x;
scanf("%d%d",&f,&x);
shu+=1;
z[shu]=x;
v[shu]=i;
gen[f]=hb1(gen[f],shu);
}
shu+=1;
v[shu]=1;
priority_queue <SJd> pq;
pq.push(SJd(0,shu));
SJd jd;
for(int i=0;i<k;i++)
{
if(pq.empty())
break;
jd=pq.top();
pq.pop();
int la=z[jd.wz],vv=v[jd.wz];
jd.wz=hb2(l[jd.wz],r[jd.wz]);
if(jd.wz!=0)
pq.push(SJd(jd.he-la+z[jd.wz],jd.wz));
jd.wz=hb2(jd.wz,gen[vv]);
if(jd.wz!=0)
pq.push(SJd(jd.he+z[jd.wz],jd.wz));
}
printf("%lld",jd.he%998244353);
return 0;
}