P6830 [IOI2020] 构造

题意

传送门 P6830 [IOI2020]连接擎天树

题解

若出现 p [ i ] [ j ] = 3 p[i][j]=3 p[i][j]=3 的情况,由于没有重边,则至少存在两条 i → j i\rightarrow j ij 的路径,满足路径上存在非 i , j i,j i,j 的节点;对于这样的两个分别属于不同路径的节点 u , v u,v u,v,它们之间至少存在四条路径,即 u → i → v , u → i → j → v , u → j → v , u → j → i → v u\rightarrow i\rightarrow v,u\rightarrow i\rightarrow j\rightarrow v,u\rightarrow j\rightarrow v,u\rightarrow j\rightarrow i\rightarrow v uiv,uijv,ujv,ujiv。于是得到推论:不存在 p [ i ] [ j ] = 3 p[i][j]=3 p[i][j]=3 的情况。

将图划分为连通分量,依次处理。对任一联通分量,两点间路径只可能有 1 , 2 1,2 1,2 两种可能;若存在 p [ i ] [ j ] = 2 p[i][j]=2 p[i][j]=2 的情况,则连通分量中存在环。可以证明,若连通分量中至多存在一个环。反证法,若图中存在两个环,若两个环之间存在割点,则存在两点 i , j i,j i,j,满足 p [ i ] [ j ] = 4 p[i][j]=4 p[i][j]=4;若两个环之间不存在割点,那么存在两个点 i , j i,j i,j,满足 p [ i ] [ j ] = 3 p[i][j]=3 p[i][j]=3

故对于存在至少一条边的联通分量,只可能为树或者基环树。对于环上节点 u u u,若不存在以其为根的子树,则对任一节点 v v v,满足 p [ u ] [ v ] = 2 p[u][v]=2 p[u][v]=2;若存在以 u u u 为根的子树,设子树接节点构成集合 S S S,那么 S S S 中的任意一对节点 i , j i,j i,j 满足 p [ i ] [ j ] = 1 p[i][j]=1 p[i][j]=1,而 S S S 中的节点 i i i 与不属于 S S S 的节点 j j j 满足 p [ i ] [ j ] = 2 p[i][j]=2 p[i][j]=2

那么对于任意连通分量,可以如下构造。划分出满足任意两点间路径为 1 1 1 的连通分量,将其任意生成树;对于这样的连通分量,取一点置于环中。满足与所有点间路径为 2 2 2 的节点置于环中。

判断上述两类连通分量的合法性,构造求解即可。总时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l, _ = r; i < _; ++i)
const int maxn = 1005;
typedef vector<int> vec;
typedef vector<vec> mat;
struct CCS
{
    int N, nlp, lp[maxn], ntr, tr[maxn];
    bool used[maxn];

    bool judge(int u, mat &p, mat &b)
    {
        ntr = 0;
        rep(v, 0, N) if (p[u][v] == 1) used[v] = 1, tr[ntr++] = v;
        rep(i, 0, ntr)
        {
            rep(j, 0, ntr) if (p[tr[i]][tr[j]] != 1) return 0;
            int cnt = 0;
            rep(j, 0, N) cnt += p[tr[i]][j] == 1;
            if (cnt != ntr)
                return 0;
        }
        lp[nlp++] = tr[0];
        rep(i, 0, ntr - 1)
        {
            int u = tr[i], v = tr[i + 1];
            b[u][v] = b[v][u] = 1;
        }
        return 1;
    }

    int construct(mat &p, int cc[], mat &B)
    {
        N = p.size();
        mat b(N, vec(N));
        memset(used, 0, sizeof(used));
        nlp = 0;
        rep(i, 0, N)
        {
            if (used[i])
                continue;
            bool hasTree = 0;
            rep(j, 0, N) if (i != j && p[i][j] == 1)
            {
                hasTree = 1;
                if (!judge(i, p, b))
                    return 0;
                break;
            }
            if (!hasTree)
                lp[nlp++] = i;
        }
        bool hasLoop = 0;
        rep(i, 0, N) rep(j, i + 1, N) if (p[i][j] == 2)
        {
            hasLoop = 1;
            break;
        }
        if (hasLoop)
        {
            if (nlp < 3)
                return 0;
            rep(i, 0, nlp)
            {
                int u = lp[i], v = lp[(i + 1) % nlp];
                b[u][v] = b[v][u] = 1;
            }
        }
        rep(i, 0, N) rep(j, 0, N) B[cc[i]][cc[j]] = b[i][j];
        return 1;
    }
} ccs;
int N, ncc, cc[maxn];
bool used[maxn];

void build(mat b);

bool judge(int u, mat &p)
{
    ncc = 0;
    rep(v, 0, N) if (p[u][v]) used[v] = 1, cc[ncc++] = v;
    rep(i, 0, ncc)
    {
        rep(j, 0, ncc) if (!p[cc[i]][cc[j]]) return 0;
        int cnt = 0;
        rep(j, 0, N) cnt += (bool)p[cc[i]][j];
        if (cnt != ncc)
            return 0;
    }
    return 1;
}

int construct(mat p)
{
    N = p.size();
    rep(i, 0, N) rep(j, 0, N) if (p[i][j] == 3) return 0;
    memset(used, 0, sizeof(used));
    mat b(N, vec(N));
    rep(i, 0, N)
    {
        if (used[i])
            continue;
        bool hasCC = 0;
        rep(j, 0, N) if (i != j && p[i][j])
        {
            hasCC = 1;
            if (!judge(i, p))
                return 0;
            break;
        }
        if (hasCC)
        {
            mat _p(ncc, vec(ncc));
            rep(j, 0, ncc) rep(k, 0, ncc) _p[j][k] = p[cc[j]][cc[k]];
            if (!ccs.construct(_p, cc, b))
                return 0;
        }
    }
    build(b);
    return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值