原题链接

算法不难,比赛的时候就和cyc大佬一起yy了正解,不过因为交的时候比较急(要回寝室惹),我有两数组开错大小直接爆到50,cyc大佬则只把文件输入关了一半,直接爆零(╯ ̄Д ̄)╯┻━┻

要尽量使\(k\)次删边都能有贡献,那么很容易就想到割边。

所以我们先用\(\mathtt{tarjan}\)跑出所有割边,而每一条割边都能产生\(1\)的贡献,因此有\(sum_{bridge}\)条割边,就能增加\(sum_{bridge}\)个连通块。

若\(k \leqslant sum_{bridge}\),那么最后的答案就是\(k + s\)(设原来的图有\(s\)个连通块)。

若\(k > sum_{bridge}\),那么先删去这些割边,则剩下\(k' = k - sum_{bridge}\)条删边的次数。

然后我们考虑剩下的图该怎么去删边使贡献最大。

很容易发现删去割边后的图就是单个简单环或多个以点连接的简单环的集合。

对于多个以点连接的简单环,实际上我们可以把这多个连接的环拆成多个单环,如下图所示:



因为虽然将多个环拆开来会多出几个点,但是与此同时也增加了相同数目的连通块,因此对拆开后的多个单环进行删边的最大贡献是与原图相同的。

然后我们考虑怎么在这单环集合中删边使得贡献最大化,显然对于一个环,总是要删去一条没有贡献的边使其变为一条链,之后的每一次删边都能产生\(1\)的贡献。

所以我们需要尽量减少第一次删去无贡献边的次数,那么贪心策略就显而易见了,即将所有环按环的大小(即边数)降序排序,然后逐个尝试删边,直到删边次数\(k'\)用光或是边被删光为止。

对于单个环删边产生的贡献,若\(k' > size_{circle}\)(设环的大小为\(size_{circle}\)),则贡献为\(size_{circle} - 1\);若\(k' <= size_{circle}\),则贡献为\(k' - 1\)。

至于如何找出多个环(已经找出割边并标记),我们可以用\(\mathtt{dfs}\),以时间戳标记的方式来找出,具体实现可以查看代码部分。

如果排序使用桶排,那么时间复杂度就是\(O(n + m)\),像我这种懒的人直接用\(\mathtt{sort}\)就会多一个\(\log\)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e6 + 10;
const int M = 2e6 + 10;
int fi[N], di[M << 1], ne[M << 1], bridge[M << 1], dfn[N], low[N], circle[N], cir_s, l = 1, ti, bridge_sum;
//fi,di,ne为邻接表,bridge储存某一边是否是割边,dfn为时间戳,low为tarjan中的追溯值,circle储存每个单环的大小。
bool fa_l[M << 1];//dfs中用来判断边是否已经走过
inline int re()//快读
{
int x = 0;
char c = getchar();
bool p = 0;
for (; c < '0' || c > '9'; c = getchar())
p |= c = '-';
for (; c >= '0' && c <= '9'; c = getchar())
x = x * 10 + c - '0';
return p ? -x : x;
}
inline void add(int x, int y) { di[++l] = y; ne[l] = fi[x]; fi[x] = l; }//加边
inline int minn(int x, int y) { return x < y ? x : y; }
bool comp(int x, int y) { return x > y; }
void tarjan(int x, int la)//tarjan找割边模板
{
int i, y;
dfn[x] = low[x] = ++ti;
for (i = fi[x]; i; i = ne[i])
if (!dfn[y = di[i]])
{
tarjan(y, i);
low[x] = minn(low[x], low[y]);
if (low[y] > dfn[x])
bridge[i] = bridge[i ^ 1] = 1, bridge_sum++;
}
else
if (i ^ la ^ 1)
low[x] = minn(low[x], dfn[y]);
}
void dfs(int x)//找出各个环
{
int i, y;
for (i = fi[x]; i; i = ne[i])
if (!bridge[i] && !fa_l[i])//如果这条边不是割边且没有走过
if (!dfn[y = di[i]])//如果边所连的点没有走过
dfn[y] = dfn[x] + 1, fa_l[i] = fa_l[i ^ 1] = 1, dfs(y);//标记时间戳;记录这条边已走过;继续搜索
else//如果走过了
fa_l[i] = fa_l[i ^ 1] = 1, circle[++cir_s] = dfn[x] + 1 - dfn[y];//同样要记录这条边已走过,否则在回溯的时候会出锅;将该环的大小计入数组
}
int main()
{
int i, n, m, k, x, y, ans = 0;
n = re(); m = re(); k = re();
for (i = 1; i <= m; i++)//输入图
{
x = re(); y = re();
add(x, y); add(y, x);
}
for (i = 1; i <= n; i++)//tarjan找割边,同时统计原图连通块的个数
if (!dfn[i])
tarjan(i, 0), ans++;
if (k <= bridge_sum)//若k不够删去所有割边,就直接输出答案
return printf("%d", k + ans), 0;
ans += bridge_sum; k -= bridge_sum;//统计答案并将k减去割边数
memset(dfn, 0, sizeof(dfn));
for (i = 1; i <= n; i++)//找环
if (!dfn[i])
dfn[i] = 1, dfs(i);
sort(circle + 1, circle + cir_s + 1, comp);
for (i = 1; i <= cir_s; i++)//贪心删边并统计贡献
if (k >= circle[i])
ans += circle[i] - 1, k -= circle[i];
else
{
ans += k - 1;
break;
}
return printf("%d", ans), 0;
}

牛客CSP-S提高组赛前集训营2 T2沙漠点列的更多相关文章

  1. 牛客网CSP-S提高组赛前集训营Round4

    牛客网CSP-S提高组赛前集训营 标签(空格分隔): 题解 算法 模拟赛 题目 描述 做法 \(BSOJ6377\) 求由\(n\)长度的数组复制\(k\)次的数组里每个连续子序列出现数字种类的和 对 ...

  2. 牛客CSP-S提高组赛前集训营1

    牛客CSP-S提高组赛前集训营1 比赛链接 官方题解 before:T1观察+结论题,T2树形Dp,可以换根或up&down,T3正解妙,转化为图上问题.题目质量不错,但数据太水了~. A-仓 ...

  3. 牛客CSP-S提高组赛前集训营3

    A 货物收集 显然是一个二分答案的题. #include<iostream> #include<cstdio> #include<cstring> #include ...

  4. 牛客CSP-S提高组赛前集训营3 赛后总结

    货物收集 二分答案.复杂度\(O(n\log n)\). 货物分组 用费用提前计算的思想,考虑用一个新的箱子来装货物会发生什么. 显然费用会加上后面的所有货物的总重. \(60\)分的\(O(n^2) ...

  5. 牛客CSP-S提高组赛前集训营2 ———— 2019.10.31

    比赛链接 期望得分:100+20+20 实际得分:40+20+30 awa  cccc T1 :基于贪心的思路,然后开始爆搜(雾 那必然是会死的,好吧他就是死了 #include<iostrea ...

  6. 牛客CSP-S提高组赛前集训营1———2019.10.29 18:30 至 22:00

    期望得分:100+0+10 实际得分:40+0+0 考炸了... T1:题目链接 究竟为什么会这样,,, 仔细研读我的丑代码 发现... 枯辽.... #include<cstdio> # ...

  7. 20191029 牛客CSP-S提高组赛前集训营1

    前一个小时看这几道题感觉要爆零 A. 仓鼠的石子游戏 分析一下发现a[i]>1a[i]>1a[i]>1时后先手必输,a[i]=1a[i]=1a[i]=1时先手必赢 然后直接看1的个数 ...

  8. 牛客CSP-S提高组赛前集训营4 赛后总结

    复读数组 分成 3 种区间算答案: 一个块内的区间 两个块交界处,长度小于块长的区间 长度不小于块长的区间 对于第三种区间,容易发现每个区间的权值一样,只需要算出个数即可. 对于前两种空间,我的思路是 ...

  9. 牛客CSP-S提高组赛前集训营5 赛后总结

    A.无形的博弈 心理题. 答案为\(2^n\),可感性理解结论的正确性. #include<bits/stdc++.h> #define LL long long const LL Mod ...

随机推荐

  1. 【牛客】路径计数机 (树形dp 前缀和)

    题目描述 有一棵n个点的树和两个整数p, q,求满足以下条件的四元组(a, b, c, d)的个数:  1.$1\leq a,b,c,d \leq n$  2.点a到点b的经过的边数为p.  3.点c ...

  2. 出现 java.lang.OutOfMemoryError: PermGen space 错误的原因及解决方法

    一.原因及解决方法[1] 1.原因:堆内存的永久保存去区内存分配不足(缺省默认为64M),导致内存溢出错误. 2.解决方法:重新分配内存大小,-Xms1024M -Xmx2048M -XX:PermS ...

  3. ubuntu之路——day18 用pytorch完成CNN

    本次作业:Andrew Ng的CNN的搭建卷积神经网络模型以及应用(1&2)作业目录参考这位博主的整理:https://blog.csdn.net/u013733326/article/det ...

  4. [技术博客]使用adb命令获取app(游戏)错误和警告日志

    adb命令的使用 直接在命令行中输入: adb logcat *:W 注意:这句命令的意思是显示所有优先级大于等于警告(Warning)的日志,查找崩溃问题一般用: adb logcat *:E 注意 ...

  5. chrome如何禁用js代码

    chrome如何禁用js代码 一.总结 一句话总结: 设置-->高级-->隐私设置和安全性-->网站设置-->javascript 中禁止javascript即可 二.chro ...

  6. 平时没有怎么用Excel做 加减乘除 计算,猛地发现,其实Excel 是一个很好的简单计算器

    平时没有怎么用Excel做 加减乘除 计算,猛地发现,其实Excel 是一个很好的简单计算器

  7. JAVA获取树形结构

    package com.nnmzkj.common.dto; import lombok.Data; import java.io.Serializable;import java.util.Arra ...

  8. lamda表达式求最小值

    //取最小设置金额的列 var minList = LsList.OrderBy(n=>n.Price).First(); //取最小设置金额的值 var minValue = LsList.S ...

  9. SpringCloud 微服务中 @Async 注解自定义线程池 引发的aop 问题

    背景 在 使用springCloud 的@Async注解来做异步操作时,想自定义其线程池. 引发问题 自定义完线程池后,发现代码里并没有使用自定义线程池里的线程,于是新建一个demo工程,一样的配置代 ...

  10. maven基础依赖外部lib包(依赖钉钉sdk为例)

    jar包放置位置 pom.xml指定依赖 1 <dependencies> 2 <!--钉钉工具包,如缺失请到钉钉服务器开发文档下载--> 3 <dependency&g ...