首页 > 其他分享 >JOISC2020 Day 4 A 首都 题解

JOISC2020 Day 4 A 首都 题解

时间:2024-08-16 14:04:45浏览次数:13  
标签:int 题解 top 1000005 JOISC2020 dep dfn include Day

JOISC2020 Day 4 A 首都

JOI AtCoder Luogu

考虑一条链的情形。

如图,将每个城市视为一条线段,容易发现交错(有交但不包含)的若干线段必须全部合并才能符合条件。但如果这么写会出错,原因是线段有包含关系,外层线段需要统计内层线段的答案,但内层线段不需要统计外层线段的答案。如果设内层线段为 \(x\),外层线段为 \(y\),则可以这样描述:

  • 如果选 \(x\) 作为首都,则不能选 \(y\)(选 \(y\) 一定更劣);
  • 如果选 \(y\) 作为首都,则必须选 \(x\)(满足作为首都的条件)。

这很像 2-SAT 问题,启发我们根据依赖关系建图:如果城市 \(a\) 的两个城镇之间的路径经过城市 \(b\),则连边 \(a \rightarrow b\)。答案即为没有出边(满足条件不依赖于合并更多城市)的最小的强连通分量的大小减 \(1\)。

暴力连边一定会超时,考虑优化建图:若当前对于城市 \(x\) 建图,类似于建虚树,取出 \(x\) 的所有城镇以及它们的所有 LCA 作为关键点。关键点之间使用树链剖分得到一段连续的链,将 \(x\) 向链上的点所属的城市分别连边。

使用 线段树优化建图,在树剖得到的 DFS 序上建立线段树,每个叶子节点代表向其代表的原树上的点所属的城市连边,线段树内部父亲向儿子连边。如此操作后,每条链 \([l, r]\) 均可分解为至多 \(O(\log n)\) 个极大区间。可以发现,新图一定与原图等价,但边的数量减小为可接受级别。

求强连通分量使用 Tarjan。

时间复杂度 \(O(n \log^2 n)\)。

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

using namespace std;

int n, k, rt;
int c[1000005];
vector<int> vec[1000005];
vector<int> G[1000005];
vector<int> include[1000005];

int f[1000005];
int dfn[1000005], dfn_clock;
int nfd[1000005];
int dep[1000005];
int son[1000005];
int top[1000005];
int siz[1000005];
static inline void dfs(int u, int fa) { // chain segmentation
    f[u] = fa;
    dep[u] = dep[fa] + 1;
    siz[u] = 1;
    for (auto v : vec[u]) {
        if (v == fa)
            continue;
        dfs(v, u);
        siz[u] += siz[v];
        if (siz[v] > siz[son[u]])
            son[u] = v;
    }
}
static inline void dfs2(int u) {
    dfn[u] = ++dfn_clock;
    nfd[dfn_clock] = u;
    if (!son[u])
        return;
    top[son[u]] = top[u];
    dfs2(son[u]);
    for (auto v : vec[u]) {
        if (v == f[u] || v == son[u])
            continue;
        top[v] = v;
        dfs2(v);
    }
}
static inline int LCA(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        u = f[top[u]];
    }
    if (dep[u] < dep[v])
        return u;
    return v;
}

struct node { // SGT optimize building graph
    int ls, rs;
} d[4000005];
int cnt;
static inline int build(int s, int t) {
    int p = ++cnt;
    if (s == t) {
        G[p].push_back(c[nfd[s]]);
        return p;
    }
    int mid = (s + t) >> 1;
    d[p].ls = build(s, mid);
    d[p].rs = build(mid + 1, t);
    G[p].push_back(d[p].ls);
    G[p].push_back(d[p].rs);
    return p;
}
static inline void addedge(int l, int r, int s, int t, int from, int p) {
    if (l <= s && r >= t) {
        G[from].push_back(p);
        return;
    }
    int mid = (s + t) >> 1;
    if (l <= mid)
        addedge(l, r, s, mid, from, d[p].ls);
    if (r > mid)
        addedge(l, r, mid + 1, t, from, d[p].rs);
}
static inline void add(int u, int v) {
    int col = c[u];
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        addedge(dfn[top[u]], dfn[u], 1, n, col, rt);
        u = f[top[u]];
    }
    if (dep[u] > dep[v])
        swap(u, v);
    addedge(dfn[u], dfn[v], 1, n, col, rt);
}

int t_dfn[1000005], t_low[1000005], t_dfn_clock;
int sta[1000005], tail;
bool insta[1000005];
vector<int> scc[1000005];
int belong[1000005];
int scc_cnt;
static inline void tarjan(int u) {
    t_dfn[u] = t_low[u] = ++t_dfn_clock;
    sta[++tail] = u;
    insta[u] = true;
    for (auto v : G[u]) {
        if (!t_dfn[v]) {
            tarjan(v);
            t_low[u] = min(t_low[u], t_low[v]);
        } else if (insta[v])
            t_low[u] = min(t_low[u], t_dfn[v]);
    }
    if (t_dfn[u] == t_low[u]) {
        ++scc_cnt;
        while (sta[tail] != u) {
            scc[scc_cnt].push_back(sta[tail]);
            belong[sta[tail]] = scc_cnt;
            insta[sta[tail]] = false;
            --tail;
        }
        scc[scc_cnt].push_back(u);
        belong[u] = scc_cnt;
        insta[u] = false;
        --tail;
    }
}

int sum[1000005];
int deg[1000005];

signed main() {
#ifndef ONLINE_JUDGE
    freopen("P7215.in", "r", stdin);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> k;
    cnt = k;
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    for (int i = 1; i <= n; ++i) {
        cin >> c[i];
        include[c[i]].push_back(i);
    }
    dfs(1, 0);
    top[1] = 1;
    dfs2(1);
    rt = build(1, n);
    for (int i = 1; i <= k; ++i) {
        if (include[i].size() > 1) { // like virtual tree
            int cur = include[i][0];
            for (size_t j = 1; j < include[i].size(); ++j) {
                int lca = LCA(include[i][0], include[i][j]);
                if (include[i][j] != lca)
                    add(include[i][j], lca);
                if (dep[lca] < dep[cur])
                    cur = lca;
            }
            if (include[i][0] != cur)
                add(include[i][0], cur);
        }
    }
    for (int i = 1; i <= cnt; ++i)
        if (!t_dfn[i])
            tarjan(i);
    for (int i = 1; i <= k; ++i)
        ++sum[belong[i]];
    for (int u = 1; u <= cnt; ++u)
        for (auto v : G[u])
            if (belong[u] != belong[v])
                ++deg[belong[u]];
    int ans = 1e9;
    for (int i = 1; i <= k; ++i)
        if (!deg[belong[i]])
            ans = min(ans, sum[belong[i]]);
    cout << ans - 1 << endl;
    return 0;
}

标签:int,题解,top,1000005,JOISC2020,dep,dfn,include,Day
From: https://www.cnblogs.com/bluewindde/p/18362721

相关文章

  • Day31 贪心算法part5
    目录任务56.合并区间思路738.单调递增的数字思路968.监控二叉树思路任务56.合并区间以数组intervals表示若干个区间的集合,其中单个区间为intervals[i]=[starti,endi]。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。思路......
  • day1打卡
    704:二分查找题目链接:https://leetcode.cn/problems/binary-search/这个还是比较简单的intsearch(vector&nums,inttarget){intlow=0;inthigh=nums.size()-1;intmid=(low+high)/2;while(nums[mid]!=target||low>=high){if(nums[mid]<target){......
  • 代码随想录 day31|| 56 合并区间,738 单调递增数字,968 监控二叉树
    56合并区间funcmerge(intervals[][]int)[][]int{ //思路先排序,然后按照后一个左区间和前一个右区间进行对比判断是否重叠,重叠扩充右区间 sort.Slice(intervals,func(i,jint)bool{ ifintervals[i][0]==intervals[j][0]{ returnintervals[i][1]<intervals[......
  • 实习记录day04尝试写一个定时任务
    前言:今天已经不太想写了....实习第四天上午通过swagger测了昨天的接口,本来以为knife4j手到擒来,没想到我网上一搜,1.9版本的时候还不叫knife4j....后来测试的时候我发现jwt怎么都塞不进请求头,努力半天最后大哥告诉我:添加保存之后要把页面给关了,然后才可以填进去,挺离谱的,不过幸运......
  • 『dsu、Trie』Day7
    StampRallykruskal重构树板子,套上二分求一下祖先即可。AND-MEXWalk注意到答案只可能是0,1,2。因为1和2显然不能同时存在。证明:可知边权序列不增,如果前面出现2则说明第1位是0,由于是与运算所以不可能有1了。判断0和1即可。0好判断,只要全不为0,也就是最后......
  • Day 33 动态规划 Part10
    300.最长递增子序列动态规划的版本是挺好理解的,dp[i]代表了以第i个数字结尾的最长递增子序列的长度,dp[0]显然为1。dp如何更新呢?i>0:dp[i]=在i之前,最大的小于nums[i]的数nums[j]dp[i]=dp[j]+1,所以就是需要找到比nums[i]小的最大的数,遍历就可以得到。classSolution......
  • 代码随想录Day16
    513.找树左下角的值给定一个二叉树的根节点root,请找出该二叉树的最底层最左边节点的值。假设二叉树中至少有一个节点。示例1:输入:root=[2,1,3]输出:1示例2:输入:[1,2,3,4,null,5,6,null,null,7]输出:7提示:二叉树的节点个数的范围是[1,104]-231<=......
  • 题解:AT_abc365_d [ABC365D] AtCoder Janken 3
    D-AtCoderJanken3题解题意:高桥和青木要玩石头剪刀布,给你一个长度为\(n\)的字符串\(s\),\(s\)表示青木在第\(i\)局游戏中的动作(R表示石头,P表示布,S表示剪刀。)。高桥不可以在任何一局中输给青木(即:高桥和青木只可以平局或高桥赢青木),且高桥第\(i\)局出的和第\(i-1\)局......
  • 题解:P10313 [SHUPC 2024] 占地斗士!
    题目大意给出一个由.和#组成的\(n\timesm\)矩阵,然后再给你这\(4\)种图像,用着四种图像对矩阵进行覆盖(每个只能用一次)。其中,#的位置不可以被图像遮挡,也不能放在不能放置的格子上。解题思路考虑使用爆搜。第一个图像:if(mp[i][j]!='#'&&mp[i+1][j+1]!='#'......
  • 题解:P10111 [GESP202312 七级] 纸牌游戏
    题目大意给出三个序列:\(a\),\(b\),\(c\)分别表示:分数,罚分以及小杨从第\(1\)轮至第\(......