牛客CSP-S提高组赛前集训营2 T2沙漠点列
原题链接
算法不难,比赛的时候就和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沙漠点列的更多相关文章
- 牛客网CSP-S提高组赛前集训营Round4
牛客网CSP-S提高组赛前集训营 标签(空格分隔): 题解 算法 模拟赛 题目 描述 做法 \(BSOJ6377\) 求由\(n\)长度的数组复制\(k\)次的数组里每个连续子序列出现数字种类的和 对 ...
- 牛客CSP-S提高组赛前集训营1
牛客CSP-S提高组赛前集训营1 比赛链接 官方题解 before:T1观察+结论题,T2树形Dp,可以换根或up&down,T3正解妙,转化为图上问题.题目质量不错,但数据太水了~. A-仓 ...
- 牛客CSP-S提高组赛前集训营3
A 货物收集 显然是一个二分答案的题. #include<iostream> #include<cstdio> #include<cstring> #include ...
- 牛客CSP-S提高组赛前集训营3 赛后总结
货物收集 二分答案.复杂度\(O(n\log n)\). 货物分组 用费用提前计算的思想,考虑用一个新的箱子来装货物会发生什么. 显然费用会加上后面的所有货物的总重. \(60\)分的\(O(n^2) ...
- 牛客CSP-S提高组赛前集训营2 ———— 2019.10.31
比赛链接 期望得分:100+20+20 实际得分:40+20+30 awa cccc T1 :基于贪心的思路,然后开始爆搜(雾 那必然是会死的,好吧他就是死了 #include<iostrea ...
- 牛客CSP-S提高组赛前集训营1———2019.10.29 18:30 至 22:00
期望得分:100+0+10 实际得分:40+0+0 考炸了... T1:题目链接 究竟为什么会这样,,, 仔细研读我的丑代码 发现... 枯辽.... #include<cstdio> # ...
- 20191029 牛客CSP-S提高组赛前集训营1
前一个小时看这几道题感觉要爆零 A. 仓鼠的石子游戏 分析一下发现a[i]>1a[i]>1a[i]>1时后先手必输,a[i]=1a[i]=1a[i]=1时先手必赢 然后直接看1的个数 ...
- 牛客CSP-S提高组赛前集训营4 赛后总结
复读数组 分成 3 种区间算答案: 一个块内的区间 两个块交界处,长度小于块长的区间 长度不小于块长的区间 对于第三种区间,容易发现每个区间的权值一样,只需要算出个数即可. 对于前两种空间,我的思路是 ...
- 牛客CSP-S提高组赛前集训营5 赛后总结
A.无形的博弈 心理题. 答案为\(2^n\),可感性理解结论的正确性. #include<bits/stdc++.h> #define LL long long const LL Mod ...
随机推荐
- 了解Vuex状态管理模式的理解强化指南
1 Vuex是什么呢?它是Vue的状态管理模式,在使用vue的时候,需要在vue中各个组件之间传递值是很痛苦的,在vue中我们可以使用vuex来保存我们需要管理的状态值,值一旦被改变,所有引用该值的地 ...
- Java将list<map>或者list<entity>集合根据指定字段排序
今天项目中用到了,特记录一下 一. List<Map> 如果 item.get(sortField) 有时间,有数字的时候直接toString(),数组结果的排序结果可能不正确 List& ...
- Javascript Date构造函数和比较 (二)
JavaScript Date对象 构造函数实例 Date构造函数中没有参数,将返回当前日期 var currentDate = new Date(); writeLine(currentDate.t ...
- Linux好用的工具
ag:比grep.ack更快的递归搜索文件内容. tig:字符模式下交互查看git项目,可以替代git命令. mycli:mysql客户端,支持语法高亮和命令补全,效果类似ipython,可以替代my ...
- 微信小程序 调用远程接口,给全局数组赋值
关键是 let that = this 因为在onLoad 的 wx.request方法里, this指的是 wx.request 的上下文, 所以应该是在 onLoad 的时候定义 let that ...
- mybatis自定义之优先从classes目录加载,加载之后遇到相同的类定义时不再加载
如果mybatis中包含了两个相同定义的mapper,启动时出现下列异常: Caused by: java.lang.IllegalArgumentException: Result Maps col ...
- 解析生效测试方法 执行命令 ping 域名 得不到 IP 主要有如下几个原因:
https://help.aliyun.com/knowledge_detail/39834.html dig https://cloud.tencent.com/document/product/3 ...
- sed 变量在shell引用
#!/bin/bashZipName=`ls -lt /data/office_services/*.zip | head -1 | awk -F"/" '{print $NF}' ...
- Java13新特性 -- switch表达式
引入了yield语句,用于返回值: 和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块. @Test public void testSwitch ...
- Maven 常用工具类整理
目录 1.Apache Commons 1.1.字符串处理 1.2.集合操作 1.3.IO操作 1.4.编解码操作 2.Google Guava 2.1.多场景使用 2.2.guava-retryin ...