Codeforces - 图论题目(难度:2000)

欢迎访问本菜鸡的独立博客:Codecho

Summary: 感觉只有 1 4 \frac{1}{4} 41 的题目比较硬核,能学到些东西;剩下的题目比较水,以 DFS 为主。

463D - Gargari and Permutations (建图 + 拓扑序上dp)

1. 题意

给你 k k k 个长度为 n n n排列,问它们的最长公共子序列的长度。

数据范围: 1 ≤ n ≤ 1 0 3 ; 2 ≤ k ≤ 5 1 \le n \le 10^3; 2 \le k \le 5 1n103;2k5

2. 解题思路

设排列 i i i 中第 j j j 个数字为 p i , j p_{i,j} pi,j,则对于每个数字,将所有的有向边 &lt; p i , j , p i , k &gt; ( j &lt; k ≤ n ) &lt;p_{i,j}, p_{i,k}&gt;(j &lt; k \le n) <pi,j,pi,k>(j<kn)边权均加 1 1 1

之后,我们得到了一个 DAG,很快想到可以在拓扑序上面进行 dp

很显然,只有边权为 k k k 的边是有用的,我们根据这个条件先对整个图进行一遍拓扑排序,把拓扑序存储到 t o p o ​ topo​ topo 数组中。

d p [ i ] ​ dp[i]​ dp[i] 为以 i ​ i​ i 为起点的 LCS 的长度。

之后,我们枚举 t o p o ​ topo​ topo 数组,对于 v = t o p o [ i ] ​ v=topo[i]​ v=topo[i],将其作为临时的终点,之后从 1 ​ 1​ 1 n ​ n​ n 枚举起点 u ​ u​ u,若 &lt; u , v &gt; ​ &lt;u,v&gt;​ <u,v> 的边权为 k ​ k​ k ,表明 d p [ v ] ​ dp[v]​ dp[v] 可以由 d p [ u ] ​ dp[u]​ dp[u] 转移过来。

遍历完之后,所有的 d p ​ dp​ dp 值都已经计算完成,其中的最大值便是答案。

时间复杂度: O ( k ⋅ n 2 ) ​ O(k \cdot n^2)​ O(kn2)

3. 代码

int a[10][maxn], head[maxn], n, k;
int G[maxn][maxn], indeg[maxn], topo[maxn], dp[maxn];

queue<int> que;
int cnt = 0;
void toposort()
{
   
    for(int i = 1; i <= n; i++)
    {
   
        if(!indeg[i])   que.push(i);
    }
    while(!que.empty())
    {
   
        int now = que.front();
        que.pop();
        topo[++cnt] = now;
        for(int i = 1; i <= n; i++)
        {
   
            if(G[now][i] != k)  continue;
            --indeg[i];
            if(!indeg[i])   que.push(i);
        }
    }
}

int main()
{
   
    scanf("%d%d", &n, &k);

    for(int i = 1; i <= k; i++)
    {
   
        for(int j = 1; j <= n; j++)
        {
   
            scanf("%d", &a[i][j]);
        }
    }


    for(int i = 1; i <= k; i++)
    {
   
        for(int j = 1; j <= n; j++)
        {
   
            for(int l = j + 1; l <= n; l++)
            {
   
                G[a[i][j]][a[i][l]]++;
            }
        }
    }

    for(int i = 1; i <= n; i++)
    {
   
        for(int j = 1; j <= n; j++)
        {
   
            if(G[i][j] == k)    indeg[j]++;
        }
    }

    toposort();

    for(int i = 1; i <= cnt; i++)
    {
   
        for(int u = 1; u <= n; u++)
        {
   
            int v = topo[i];
            if(G[u][v] == k)
            {
   
                dp[v] = max(dp[v], dp[u] + 1);
            }
        }
    }


    int ans = 0;
    for(int i = 1; i <= n; i++) ans = max(ans, dp[i]);

    printf("%d\n", ans + 1);
    return 0;
}

449B - Jzzhu and Cities (Dijkstra 松弛条件改造)

1. 题意

给你一个有 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2n105) 个节点的无向图 1 1 1 为其源点。

现在有两种边

( 1 ) ​ (1)​ (1) m ( 1 ≤ m ≤ 3 ⋅ 1 0 5 ) ​ m(1 \le m \le 3 \cdot 10^5)​ m(1m3105) 条道路,连接城市 u i ​ u_i​ ui v i ​ v_i​ vi,长度为 x i ​ x_i​ xi

( 2 ) (2) (2) k ( 1 ≤ k ≤ 1 0 5 ) k(1 \le k \le 10^5) k(1k105) 条铁路,连接城市 1 1 1 x i x_i xi,长度为 y i y_i yi

最多删掉多少条铁路,使得 1 1 1 号城市到每个城市的最短路径的长度不变。

图保证连通,存在重边,不存在自环。

2. 解题思路

存边的时候,多存一个参数 t y p e type type,表示边的类型, 0 0 0 为道路, 1 1 1 为铁路。

f a v fa_v fav 为松弛 v v v 的边的 t y p e type type d v d_v dv 1 1 1 v v v 的最短路径的长度。

对于边 ( u , v , w ) (u,v,w) (u,v,w),有两个松弛条件

( 1 ) (1) (1) d v &gt; d u + w d_v &gt; d_u + w dv>du+w,则按照原来的思路进行松弛,并将 v v v 加入到堆中。此时更新 f a v fa_v fav 为当前边的 t y p e type type

( 2 ) (2) (2) d v = d u + w d_v = d_u + w dv=du+w,则让类型为 0 0 0 的边替换类型为 1 1 1 的边,更新 f a v fa_v fav 0 0 0

跑完 Dijkstra 之后,统计一下 f a v fa_v fav 1 1 1 的点的个数 n u m num num,则答案为 k − n u m k-num knum

时间复杂度: O ( ( n + m + k ) log ⁡ n ) ​ O((n+m+k)\log n)​ O((n+m+k)logn)

3. 代码

int n, m, k, cnt;
int head[maxn], fa[maxn];
ll d[maxn];
bool vis[maxn];

struct edge
{
   
    int v, nxt, type;
    ll w;
} Edge[8 * maxn];

struct node
{
   
    ll d;
    int id;
    node(ll _d, int _id):
        d(_d), id(_id) {
   }
    const bool operator < (const node b) const
    {
   
        return d > b.d;
    }
};

void init()
{
   
    for(int i = 0; i <= n; i++)
    {
   
        head[i] = -1;
        fa[i] = -1;
    }
    cnt = 0;
}

void addedge(int u, int v, ll w, int type)
{
   
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].type = type;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}

priority_queue<node> que;

void Dijkstra()
{
   
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    que.push(node(d[1], 1));
    while(!que.empty())
    {
   
        node now = que.top();
        que.pop();
        if(vis[now.id]) continue;
        vis[now.id] = true;
        for(int i = head[now.id]; i != -1; i = Edge[i].nxt)
        {
   
            int v = Edge[i].v;
            ll w = Edge[i].w;
            if(d[v] > d[now.id] + w)
            {
   
                d[v] = d[now.id] + w;
                fa[v] = Edge[i].type;
                que.push(node(d[v], v));
            }
            else if(d[v] == d[now.id] + w)
            {
   
                if(Edge[i].type == 0)
                {
   
                    que.push(node(d[v], v));
                    fa[v] = 0;
                }
            }
        }
    }
}

int main()
{
   
    int u, v;
    ll w;
    scanf("%d%d%d", &n, &m, &k);

    init();

    for(int i = 1; i <= m; i++)
    {
   
        scanf("%d%d%lld", &u, &v, &w);
        addedge(u, v, w, 0);
        addedge(v, u, w, 0);
    }

    for(int i = 1; i <= k; i++)
    {
   
        scanf("%d%lld", &v, &w);
        addedge(1, v, w, 1);
        addedge(v, 1, w, 1);
    }

    Dijkstra();

    int ans = 0;
    for(int i = 2; i <= n; i++) ans += fa[i];

    printf("%d\n", k - ans);
    return 0;
}

558C - Amr and Chemistry (建图 + 换根法)

1. 题意

给你 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个数 a i ( 1 ≤ a i ≤ 1 0 5 ) a_i(1 \le a_i \le 10^5) ai(1ai105),对于每个数,可对其进行若干次如下操作之一:

( 1 ) (1) (1) a i ← 2 ⋅ a i a_i \leftarrow 2 \cdot a_i ai2ai

( 2 ) (2) (2) a i ← ⌊ a i 2 ⌋ a_i \leftarrow \lfloor \frac{a_i}{2} \rfloor ai2ai

最少的操作次数,使得所有数字一样。

2. 解题思路

我们可以通过建立一个有向图来解决该问题。

设有向边 &lt; u , v &gt; &lt;u,v&gt; <u,v> 表示数 v v v 可以通过一步操作转化为数 u u u

按照这种思路,对于数 a ​ a​ a,可以建立有向边 &lt; ⌊ a 2 ⌋ , a &gt; ​ &lt;\lfloor \frac{a}{2} \rfloor, a&gt;​ <2a,a> &lt; a , 2 ⋅ a &gt; ​ &lt;a, 2 \cdot a&gt;​ <a,2a>

建边的时候不要重复建边或者超范围 ( [ 1 , 1 0 5 ] [1,10^5] [1,105]) 建边 。

因为序列 a a a 中可能出现重复数字,因此需要记录每个数字 i i i 的出现次数 w i w_i wi

假设 1 ​ 1​ 1 为根,设 d p u ​ dp_u​ dpu 表示以 u ​ u​ u 为根的子树上面出现的数字(即在序列 a ​ a​ a 中出现的数字)的个数。

d e p t h i ​ depth_i​ depthi 为点 i ​ i​ i 所在的深度,设 d e p t h 1 = 0 ​ depth_1=0​ depth1=0

我们可以通过一遍 DFS 求出所有的 d p u dp_u dpu d e p t h i depth_i depthi

a n s i ans_i ansi 表示将所有的数字最终化为 i i i 所需的最小步数。

则有
a n s 1 = ∑ i = 2 1000 w i ⋅ d e p t h i ans_1=\sum_{i=2}^{1000}{w_i\cdot depth_i} ans1=i=21000widepthi
基于 a n s 1 ans_1 ans1,我们可以再进行一次 DFS,利用换根法,计算出所有的 a n s i ans_i ansi

设当前遍历到点 i d id id 和边 &lt; i d , v &gt; &lt;id,v&gt; <id,v>,则有
a n s v = a n s i d − 1 ⋅ d p v + 1 ⋅ ( d p 1 − d p v ) ans_v=ans_{id}- 1 \cdot dp_v+ 1 \cdot (dp_1-dp_v) ansv=ansid1dpv+1(dp1dpv)
这个式子的意思是,将 v v v 及其子树中点的深度均减去 1 1 1,将其他点的深度均加上 1 1 1,即得到了 d p v dp_v dpv

但是这里需要注意的是,奇数 i i i整棵树的树根当且仅当 d p i = n dp_i=n dpi=n其他奇数都不可以做整棵树的树根

因为若 d p i &lt; n dp_i&lt;n dpi<n,表明有不在 i i i 子树中的数字,因而涉及到从小到大的转换。

而从小到大的转换无法将一个数转化为奇数

因此,需要对这一情况进行特判

最终的答案为
min ⁡ 1 ≤ i ≤ 1 0 5 { a n s i } \min_{1 \le i \le 10^5}\left\{ans_i\right\} 1i105min{ ansi}
时间复杂度: O ( 1 0 5 log ⁡ 1 0 5 ) O(10^5 \log 10^5) O(105log105)

3. 代码

int a[maxn], head[maxn], n, cnt;
ll dp[maxn], w[maxn], depth[maxn];
ll ans[maxn];

struct edge
{
   
    int v, nxt;
} Edge[4 * maxn];

void init()
{
   
    for(int i = 0; i <= 100000; i++) head[i] = -1;
    cnt = 0;
}

void addedge(int u, int v)
{
   
    Edge[cnt].v = v;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}

void dfs(int id)
{
   
    dp[id] = w[id];
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
   
        int v = Edge[i].v;
        depth[v] = depth[id] + 1;
        dfs(v);
        dp[id] += dp[v];
    }
}

void dfs2(int id)
{
   
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
   
        int v = Edge[i].v;
        if(v % 2 == 1 && dp[v] != n)    ans[v] = 0x3f3f3f3f;
        else ans[v] = ans[id] - dp[v] + (dp[1] - dp[v]);
        dfs2(v);
    }
}

int main()
{
   
    scanf("%d", &n);

    for(int i = 1; i <= n; i++)
    {
   
        scanf("%d", &a[i]);
        w[a[i]]++;
    }
    init();

    for(int i = 1; i <= 100000; i++)
    {
   
        if(i * 2 <= 100000)
        {
   
            addedge(i, i * 2);
        }
        if(i / 2 >= 1)
        {
   
            if(i % 2 == 1)  addedge(i / 2, i);
        }
    }

    dfs(1);
    for(int i = 2; i <= 100000; i++)    ans[1] += w[i] * depth[i];
    dfs2(1);

    ll res = 0x3f3f3f3f3f3f3f3f;
    for(int i = 1; i <= 100000; i++)
    {
   
        res = min(res, ans[i]);
    }

    printf("%lld\n", res);
    return 0;
}

739B - Alyona and a tree (树上差分)

1. 题意

给你一棵 n ( 1 ≤ n ≤ 2 ⋅ 1 0 5 ) n(1 \le n \le 2 \cdot 10^5) n(1n210

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值