题意:
给出一颗有根树,每次可以从树上取包括根节点的一个连通块;
定义连通块的权值为块内边的权值之和;
询问第k小的连通块的权值是多少;
n<=100000,k<=100000;
此题为CH弱省胡策#1T3;
题解:
PoPoQQQ大爷好神!
这道题也是利用A*搜索来求K大值,但是状态比较难以表示;
先考虑怎么搜索,对于一个已经选完了的点集,下一次可能再选的点有哪些?
可能是上一次选的点的儿子,也可能是回溯到上一层,选比上一层选的点大的下一个点;
注意第二个可能性,这个是维护了一个有序性,保证方案权值递增并且不会出现重复方案;
这是一个A*的过程,我说的似乎不太好理解。。
每一种状态加入优先队列来跑A*;
贴个图来演示一下:
所以每次是将这些边集中最小的边拿出来,那就要用到一个堆;
而如果对于每个状态搞一个堆妥妥MLE;
那就用可持久化左偏树来搞(可持久化数据结构get√);
这样空间复杂度是O((n+k)logn)的(大概);
当然,将一个结点的所有儿子加入堆不能暴力,对每个结点维护一个儿子可并堆就好了;
代码:
#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
#define pr pair<ll,heap*>
using namespace std;
typedef long long ll;
struct heap
{
heap *l, *r;
int dis, no, val;
heap();
}*null = new heap(), *root[N];
priority_queue<pr,vector<pr> ,greater<pr> >q;
heap::heap()
{
l = null, r = null;
dis = 0;
}
heap* Insert(heap *x, heap *y)
{
if (x == null || y == null)
return x == null ? y : x;
if (x->val > y->val)
swap(x, y);
x->r = Insert(x->r, y);
if (x->l->dis < x->r->dis)
swap(x->l, x->r);
x->dis = x->r->dis + 1;
return x;
}
heap* merge(heap *x, heap *y)
{
if (x == null || y == null)
return x == null ? y : x;
if (x->val > y->val)
swap(x, y);
heap *p = new heap();
*p = *x;
p->r = merge(p->r, y);
if (p->l->dis < p->r->dis)
swap(p->l, p->r);
p->dis = p->r->dis + 1;
return p;
}
int main()
{
int n, m, i, j, k, x, y, v;
ll ans;
heap *p;
null->l = null->r = null;
null->no = null->val = 0;
scanf("%d%d",&n,&k);
for (i = 1; i <= n; i++) root[i] = null;
for (i = 1; i < n; i++)
{
scanf("%d%d", &x, &v);
p = new heap();
p->no = i+1, p->val = v;
root[x]=Insert(root[x], p);
}
p = new heap();
p->no = 1, p->val = 0;
q.push(pr(0, p));
for (i = 1, ans = 0; i <= k&&!q.empty(); i++)
{
pr now = q.top();
q.pop();
ans = now.first;
p = now.second;
x = p->no;
v = p->val;
p = merge(p->l, p->r);
if(p!=null)
q.push(pr(now.first - v + p->val, p));
p = merge(p, root[x]);
if(p!=null)
q.push(pr(now.first + p->val, p));
}
printf("%d\n", ans % 998244353);
}