首页 > 其他分享 >题解:P5680 [GZOI2017] 共享单车

题解:P5680 [GZOI2017] 共享单车

时间:2024-08-25 20:48:12浏览次数:7  
标签:pq int 题解 P5680 ret maxn GZOI2017 dp dis

题目分析

出题人是擅长隐藏题意的

建树

首先给你一张无向图,然后指定一个根节点 \(k\),从根节点开始沿最短路到每一个节点。如果到某个节点有多条最短路径,选择上一个节点编号最短的。

考虑记录前驱的 Dijkstra。

namespace DJ
{
    int dis[maxn], pre[maxn], val[maxn], vis[maxn];
    priority_queue<pair<int, int>> pq;
    void Dijkstra(int s)
    {
        memset(dis, 0x3f, sizeof dis);
        dis[s]=0;
        pre[s]=0;
        pq.emplace(0, s);
        while(!pq.empty())
        {
            auto [d, u]=pq.top();
            pq.pop();
            if(vis[u]) continue;
            vis[u]=1;
            for(auto [v, w]:e[u])
            {
                if(dis[u]+w<dis[v])
                {
                    dis[v]=dis[u]+w;
                    pre[v]=u;
                    val[v]=w;
                    pq.emplace(-dis[v], v);
                }
                else if(dis[u]+w==dis[v])
                    pre[v]=min(pre[v], u),
                    val[v]=w;
            }
        }
    }

    void search(int x) // 记录路径
    {
        while(vis[x]&&pre[x])
        {
            e[pre[x]].emplace_back(x, val[x]);
            vis[x]=0;
            x=pre[x];
        }
    }
}

询问

首先用 \(tag_i\) 记录第 \(i\) 个点是否为投放区域。

对于 0 操作,就是翻转点的标记。

if(op==0)
    for(int i=1, t;i<=num;i++) 
        cin>>t, tag[t]^=1;

对于 1 操作,题目说的很清楚,先以 \(k\) 和询问点建一棵虚树。

前置知识:虚树

d.clear();
d.push_back(k);
for(int i=1, t;i<=num;i++) 
    cin>>t, d.emplace_back(t);
sort(d.begin(), d.end(), cmp);
for(int i=1;i<=num;i++)
    d.push_back(LCA(d[i], d[i-1]));
sort(d.begin(), d.end(), cmp);
auto end_it=unique(d.begin(), d.end());
for(auto it=d.begin()+1;it!=end_it;it++)
{
    int lc=LCA(*(it-1), *it);
    g[lc].emplace_back(*it, dis[*it]-dis[lc]);
}

然后要求让根节点和每个有标记的节点均不连通,所以考虑虚树上树形dp

令 \(dp_u\) 表示虚树上使 \(u\) 与以 \(u\) 为根的子树中的关键点断开的最小代价。

易得转移方程:

如果 \(tag_v=1\),那么它对 \(dp_u\) 的贡献为 \(\operatorname{dis}(u, v)\)。

如果 \(tag_v=0\),那么它对 \(dp_u\) 的贡献为 \(\min(\operatorname{dis}(u, v), dp_v)\)。

int dp(int x)
{
    int ret=0;
    for(auto [v, w]:g[x])
    {
        int f=dp(v);
        if(tag[v]) ret+=w;
        else ret+=min(w, f);
    }
    g[x].clear();
    return ret;
}

因为边权不为 \(0\),所以如果 dp 的结果为 \(0\),那么就证明没有被标记的投放区域,特判一下,输出 -1

Code

#include<bits/stdc++.h>
using namespace std;
#define maxn 50004

vector<pair<int, int>> e[maxn], g[maxn];

namespace DJ
{
    int dis[maxn], pre[maxn], val[maxn], vis[maxn];
    priority_queue<pair<int, int>> pq;
    void Dijkstra(int s)
    {
        memset(dis, 0x3f, sizeof dis);
        dis[s]=0;
        pre[s]=0;
        pq.emplace(0, s);
        while(!pq.empty())
        {
            auto [d, u]=pq.top();
            pq.pop();
            if(vis[u]) continue;
            vis[u]=1;
            for(auto [v, w]:e[u])
            {
                if(dis[u]+w<dis[v])
                {
                    dis[v]=dis[u]+w;
                    pre[v]=u;
                    val[v]=w;
                    pq.emplace(-dis[v], v);
                }
                else if(dis[u]+w==dis[v])
                    pre[v]=min(pre[v], u),
                    val[v]=w;
            }
        }
    }

    void search(int x)
    {
        while(vis[x]&&pre[x])
        {
            e[pre[x]].emplace_back(x, val[x]);
            vis[x]=0;
            x=pre[x];
        }
    }
}

namespace slpf
{
    int son[maxn], fa[maxn], siz[maxn], dep[maxn], dis[maxn];
    void dfs1(int u, int f)
    {
        fa[u]=f;
        siz[u]=1;
        dep[u]=dep[f]+1;
        for(auto [v, w]:e[u])
            if(v!=f) 
            {
                dis[v]=dis[u]+w;
                dfs1(v, u);
                siz[u]+=siz[v];
                if(siz[v]>siz[son[u]]) son[u]=v;
            }
    }

    int top[maxn], dfn[maxn];
    void dfs2(int u, int t)
    {
        dfn[u]=++*dfn;
        top[u]=t;
        if(!son[u]) return;
        dfs2(son[u], t);
        for(auto [v, w]:e[u])
            if(!dfn[v])
                dfs2(v, v);
    }

    int LCA(int x, int y)
    {
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]]) swap(x, y);
            x=fa[top[x]];
        }
        return dep[x]<dep[y]?x:y;
    }
}

using slpf::dfn;
using slpf::dis;
using slpf::LCA;

int tag[maxn];
vector<int> d;

int dp(int x)
{
    int ret=0;
    for(auto [v, w]:g[x])
    {
        int f=dp(v);
        if(tag[v]) ret+=w;
        else ret+=min(w, f);
    }
    g[x].clear();
    return ret;
}

int main()
{
    int n, m, k, q;
    cin>>n>>m>>k>>q;
    for(int i=1, u, v, w;i<=m;i++)
    {
        cin>>u>>v>>w;
        e[u].emplace_back(v, w);
        e[v].emplace_back(u, w);
    }

    DJ::Dijkstra(k);
    for(int i=1;i<=n;i++) e[i].clear();
    for(int i=1;i<=n;i++) DJ::search(i);

    auto cmp=[&](int a, int b){return dfn[a]<dfn[b];};

    slpf::dfs1(k, 0);
    slpf::dfs2(k, k);

    while(q--)
    {
        int op, num;
        cin>>op>>num;
        if(op==0)
            for(int i=1, t;i<=num;i++) 
                cin>>t, tag[t]^=1;
        else
        {
            d.clear();
            d.push_back(k);
            for(int i=1, t;i<=num;i++)
                cin>>t, d.emplace_back(t);

            sort(d.begin(), d.end(), cmp);
            for(int i=1;i<=num;i++)
                d.push_back(LCA(d[i], d[i-1]));
            sort(d.begin(), d.end(), cmp);
            auto end_it=unique(d.begin(), d.end());
            for(auto it=d.begin()+1;it!=end_it;it++)
            {
                int lc=LCA(*(it-1), *it);
                g[lc].emplace_back(*it, dis[*it]-dis[lc]);
            }

            int otp=dp(k);
            cout<<(otp?otp:-1)<<'\n';
        }
    }
}

标签:pq,int,题解,P5680,ret,maxn,GZOI2017,dp,dis
From: https://www.cnblogs.com/redacted-area/p/18379514

相关文章

  • 题解:SP22382 ETFD - Euler Totient Function Depth
    题目链接:link,点击这里喵。前置知识:【模板】线性筛素数,欧拉函数,点击这里喵。题意简述:给定整数$l,r,k$,求出$[l,r]$中有多少个整数不断对自己取欧拉函数刚好$k$次结果为$1$。思路:看眼数据范围,$10^{10}$的量级显然不容我们每次暴力,故考虑预处理$\varphi(i),can(i,k),su......
  • 【面试系列】大数据平台常见面试题解答
    欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏:工......
  • AtCoder ABC 368题解
    前言本题解部分思路来自于网络。A-Cut题目大意有\(n\)张卡片叠在一起,从上到下给出\(n\)卡片的编号,将\(k\)张卡片从牌堆底部放到顶部后,从上到下输出卡片的编号。解题思路按照题意模拟即可。code#include<bits/stdc++.h>usingnamespacestd;inta[105];intmai......
  • SP666 VOCV - Con-Junctions 题解
    注意到这个问题具有最优子结构性,考虑树上dp。记$dp[i][0/1]$表示i号节点不放灯或放灯的最小值,$s[i][0/1]$为对应的方案数。那么我们可以进行如下转移:$dp[u][0]=\sum_{u->v}dp[v][1]$$dp[u][1]=\sum_{u->v}\min(dp[v][0],dp[v][1])$在更新对应的dp数组时,我们用......
  • P9482 [NOI2023] 字符串 题解
    题目描述\(T\)组数据,给定长为\(n\)的字符串\(s\),\(q\)次询问,给定\(i,r\),求有多少个\(l\)满足:\(1\lel\ler\)。\(s[i:i+l-1]\)字典序小于\(R(s[i+l:i+2l-1])\)。数据范围\(1\leT\le5,1\len,q\le10^5,1\lei+2r-1\len\)。时间限制\(\texttt{1s}\),......
  • Triple Attack 题解
    直接暴力显然不可行。我们容易发现,变量\(T\)的增量以\(3\)为循环,一次循环会造成\(5\)的贡献,所以我们容易想到对每个\(a_i\)直接对\(5\)计算倍数和取余,然后对于余数分类讨论去增加,然后对于倍数部分统一增加即可。有些细节。Code#include<bits/stdc++.h>//#include......
  • Minimum Steiner Tree 题解
    原题,详见P10723。几乎相同,我们只需要以一个需要选择的点为根,遍历子树看看有没有出现需要选择的点,然后直接去删除即可,可以看我的博客。但是我们也可以换一种做法,用类似拓扑排序的算法。先找到所有只连一条边且没有被选择的点,然后放进队列,接着依次取出队头遍历与它相连的点,同时记......
  • k8s中coredns访问连接拒绝问题解决
    问题现象1、节点访问coredns连接拒绝2、内部pod无法正常进行解析问题解决思路检查CoreDNSPod状态是否正常[root@k8s-master01~]#kubectlgetpods-nkube-system-lk8s-app=kube-dnsNAMEREADYSTATUSRESTARTSAGEcoredns-7b8d6fc5......
  • CSP-J 2023 初赛试题解析(第三部分:完善程序(1-2))
    第一题补全后完整代码:#include<iostream>#include<vector>usingnamespacestd;intfind_missing(vector<int>&nums){intleft=0,right=nums.size()-1;while(left<right){intmid=left+(right-left)/2;if(nums[mi......
  • 洛谷SCP 2024 第一轮(初赛 J 组)模拟题解析(第三部分:完善程序(1-2))
    完善程序一(补全)#include<bits/stdc++.h>usingnamespacestd;constintMAXN=100000;intn;intvis[MAXN],a[MAXN];vector<int>ans;intcheck(intk){intx=n,top=0;for(inti=0;i<=k;i++)vis[i]=0;while(x......