原题链接

算法不难,比赛的时候就和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. 灰度图像--图像分割 Prewitt算子

    学习DIP第45天 转载请标明本文出处:http://blog.csdn.net/tonyshengtan,欢迎大家转载,发现博客被某些论坛转载后,图像无法正常显示,无法正常表达本人观点,对此表示很不 ...

  2. shell expect的简单实用

    一.在shell脚本中嵌入expect来实现密码输入 expect是一个自动交互功能的工具.expect是开了一个子进程,通过spawn来执行shell脚本,监测到脚本的返回结果,通过expect判断 ...

  3. element UI中的select选择器的change方法需要传递多个值

    如果直接调用change事件,不传任何参数,则可以获取到当前选中的值(因为默认会将event参数传递过去) 场景: 你需要将select选择器 ”选中的当前元素“ 和 ”其他你需要的值“ 一起传递过去 ...

  4. SVN提示is already locked 解决办法

    当svn提示is already locked ,反复clean up也无用, 可以在cmd下进入到目标文件夹的目录 执行svn cleanup 等待执行成功,就可以update了

  5. Tensorflows安装(cpu版最简安装方法)

    一.说明 首先声明,本人系统是Windows10 64位,Win7未试. 本文旨在帮助园友以更简单的方式安装Tensorflow,下面介绍的是如何安装Python的Tensorflow cpu版本. ...

  6. 013 mint-ui

    一:概述 1.中文文档 https://mint-ui.github.io/#!/en 2.安装包 二:mt-button的使用 主要是介绍css-component 1.程序 在App.vue下面写 ...

  7. scrapy之CrawlSpiders

    CrawlSpiders 通过下面的命令可以快速创建 CrawlSpider模板 的代码: scrapy genspider -t crawl loaderan cnblogs.com class s ...

  8. debian8 vga 文本模式下出现闪屏

    这种问题是因为 grub 里面关于 分辨率大小不对的问题. 在 debian 里面,在文件 /boot/grub/grub.cfg 里面可以添加 vga 参数配置. 如下: 在 kernel 启动参数 ...

  9. JAVA中String空对象的字符串拼接

    今天使用JSONObject中get一个不存在的对线,最后拼接成sql语句插入数据库时,最后数据库中的值为字符串'null',而不是空对象. 追踪许久才发现自己的java白学了. java strin ...

  10. ThinkPhp5 数据迁移(think-migration)

    ThinkPhp5 数据迁移(think-migration)   在thinkphp5中提供了数据迁移工具(think-migration),它是机遇phinx开发(文档地址:http://docs ...