Processing math: 100%

[SDOI2011]消耗战

题意

Here

简要题意:给定一颗树,树边带权,给 m 个询问,每次给 k 个点,询问删除若干条边使得这 k 个点都不与 1 号点联通的最小代价。ki500000,1m

思考

同时也记录一下虚树的学习。

看完题目,第一时间想到树形 dp,每次 O(n),总复杂度 O(nm),但是这个 m 的数据范围太迷了,肯定 TTTTTLE,题中又给了 k 的数据范围,我们难道要从 k 下手吗?

考虑每次树形 dp,我们每次要遍历 n 个点,但是显然每次有用的只有 k 个点,我们能否只利用这 k 个点建出一颗小树,这样的话每次 dp 的复杂度是 O(ki),总复杂度 O(ki)

虚树可以解决这个问题,不过我们发现,两点间需要靠它们的 lca 产生联系,那么虚树中还要存在 lca,我们发现每加入一个点,至多新加入一个 lca,那么虚树中点的上界是 2kdp的理论总复杂度的上界是 O(2ki)

关于虚树的建立,首先我们处理出各个点的 dfn 序,将询问点按 dfn 从小到大排序。
我们还有一个栈(默认根节点为 1,那么栈底元素为 1),这个栈储存的是从 1 号点到 栈顶元素 的链的信息。考虑每次插入一个询问点,设x=S[top],y=S[top1],插入点为 u

  1. lca(x,u)=xux 的子节点,那么 u 还在同一条链上,直接插入。
  2. lca(x,u) !=x,那么 LCAx 这条链已经遍历完了,我们将这条子链上的点连边(具体按 dfn 序判断),再插入 u

以上只是一个简略的过程,具体可以看代码并手动模拟一下,其实很简单。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read(){
    ll 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*10+ch-'0';ch=getchar();}
    return x * f;
}
const ll N = 250050;
const ll oo = 0x3f3f3f3f3f3f3f3f;
struct node{
    ll nxt, to, dis;
}edge[N << 1];
ll head[N], num;
void build(ll from, ll to, ll dis){
    edge[++num].nxt = head[from];
    edge[num].to = to;
    edge[num].dis = dis;
    head[from] = num;
}
ll d[N], MIN[N], f[N][20], dfn[N], dfs_clock;
void dfs(ll u, ll fa){
    dfn[u] = ++ dfs_clock;
    for(ll i=head[u]; i; i=edge[i].nxt){
        ll v = edge[i].to, dist = edge[i].dis;
        if(v == fa) continue;
        d[v] = d[u] + 1;
        f[v][0] = u;
        MIN[v] = min(MIN[u], dist);
        dfs(v, u);
    }
}
ll lca(ll u, ll v){
    if(d[u] < d[v]) swap(u, v);
    for(ll i=18; i>=0; i--) if(d[f[u][i]] >= d[v]) u = f[u][i];
    if(u == v) return u;
    for(ll i=18; i>=0; i--) if(f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
    return f[u][0];
}
vector<ll> G[N];
void add(ll u, ll v){ G[u].push_back(v); }
ll S[N], top;
void insertx(ll u){
    if(top == 1) { S[++top] = u; return; }
    ll LCA = lca(u, S[top]);
    if(LCA == S[top]) return;
    while(top > 1 && dfn[S[top - 1]] >= dfn[LCA]) add(S[top - 1], S[top]), top --;
    if(S[top] == LCA) S[++top] = u;
    else add(LCA, S[top]), S[top] = LCA, S[++top] = u;
}
ll ans[N];
void dp(ll u){
    ans[u] = MIN[u];
    if(G[u].size() == 0) return;
    ll sum = 0;
    for(ll i=0; i<G[u].size(); i++){
        ll v = G[u][i];
        dp(v);
        sum += ans[v];
    }
    ans[u] = min(ans[u], sum);
    G[u].clear();
}
ll ask[N];
bool cmp(ll x, ll y){
    return dfn[x] < dfn[y];
}
ll n, m;
int main(){
    n = read();
    for(ll i=1; i<=n-1; i++){
        ll u = read(), v = read(), d = read();
        build(u, v, d); build(v, u, d);
    }
    memset(MIN, 0x3f, sizeof(MIN)); d[1] = 1;
    dfs(1, 0);
    for(ll j=1; j<=18; j++)
        for(ll i=1; i<=n; i++) f[i][j] = f[f[i][j-1]][j-1];
    m = read();
    while(m --){
        S[1] = 1; top = 1;
        ll k = read();
        for(ll i=1; i<=k; i++) ask[i] = read();
        sort(ask + 1, ask + k + 1, cmp);
        for(ll i=1; i<=k; i++) insertx(ask[i]);
        while(top > 1) add(S[top-1], S[top]), top --;
        dp(1);
        cout << ans[1] << endl;
    }
    return 0;
}

总结

由于每次建虚树之后要清空边,注意只清空询问的点的边。

数据范围没标清的话记得开 long long

posted @   alecli  阅读(154)  评论(0编辑  收藏  举报
编辑推荐:
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
阅读排行:
· C# 13 中的新增功能实操
· Ollama本地部署大模型总结
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
· langchain0.3教程:从0到1打造一个智能聊天机器人
· 用一种新的分类方法梳理设计模式的脉络
点击右上角即可分享
微信分享提示