【牛客】沙漠点列(点双联通分量)

前言

  Happy Halloween!

题目描述

  我们称一张无向图是仙人掌,当且仅当这张无向图连通且每条边最多属于一个简单环。我们称一张无向图是沙漠,当且仅当这张无向图中所有连通子图都是仙人掌。
  给出一个 n 个点,m条边的沙漠,你可以删去其中的 k 条边。求能分成的连通块数量最大值。

输入描述:

  第一行输入三个自然数 n,m,k(3≤n≤106;0≤k≤m≤2×106)。
  接下来 m 行,每行输入两个正整数 u,v(1≤u,v≤n)保证无重边、无自环。

输出描述:

  输出一行一个正整数,表示答案。
示例1
  输入
  5 5 3
  1 2
  2 3
  2 4
  2 5
  4 5
输出
  3
说明
  一种最优的方案是:删去 2−4,2−5,4−5这三条边,剩下三个连通块:{1,2,3},{4},{5}。

分析

  看错题写了边双,还好出题人给了边双80分

  显然贪心,先割掉割边,那么剩下的就是一堆环了

  根据仙人掌的性质,我们可以猜测知道环与环最多交于一个点,

  这种情况找环应该用点双联通分量

  当有两个环交于一点时,完全可以把另一个环缩成交的那个点

  所以只需要对每个环单独考虑贡献就好了

  每个环必须先割掉一条边,剩下的就都是割边了

  人生第一次写读优

  Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1000005;
const int maxm=2000005;
int n,m,k,ecnt;
int info[maxn],nx[maxm<<1],v[maxm<<1];
int en,id,ans,cnt,dfn[maxn],low[maxn],ori[maxn],sta[maxn],siz[maxn];
int rd()
{
    char c;int x=0;c=getchar();while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();return x;
}
void add(int u1,int v1){nx[++ecnt]=info[u1];info[u1]=ecnt;v[ecnt]=v1;}
void dfs(int x)
{
    sta[++en]=x;dfn[x]=low[x]=++id;
    for(int e=info[x];e;e=nx[e])if(!dfn[v[e]])
    {
        dfs(v[e]),low[x]=min(low[x],low[v[e]]);
        if(low[v[e]]>=dfn[x])
        {
            if(sta[en]==v[e]){if(k)k--,ans++;en--;}
            else {cnt++; do siz[cnt]++;while(sta[en--]!=v[e]);}
        }
    }
    else low[x]=min(low[x],dfn[v[e]]);
}
int main()
{
    n=rd();m=rd();k=rd();
    for(int i=1,u1,v1;i<=m;i++)u1=rd(),v1=rd(),add(u1,v1),add(v1,u1);
    for(int i=1;i<=n;i++)if(!dfn[i])dfs(i),ans++;
    sort(siz+1,siz+1+cnt);
    for(int i=cnt,tmp;i>=1&&k;i--)k--,tmp=min(siz[i],k),ans+=tmp,k-=tmp;
    printf("%d
",ans);
}
原文地址:https://www.cnblogs.com/firecrazy/p/11777741.html