Description

给你一个有向图,要求重新建出一张点数相同有向图,使得点的联通关系和原图一致且边数最小。

Solution

显然对于图上的一个强连通分量跑个缩点然后把每个强连通分量都变成一个环即可。

至此,原图转化为一个\(\text{DAG}\)。

一个结论:建出来的图中出现的边一定在原图中出现过

证明

若新图中的一条边在原图中没有出现,则有两种情况:

1.这条边引入了新的联通关系,则不符题意。

2.否则,把这条边删掉会更优。

暴力

所以,我们只需考虑原图中的边是否需要被留下就行了。

考虑原图中的一条边\((u,v)\)

如果还有一个点\(s\),使得存在\(u\)到\(s\)的路径,也存在\(s\)到\(v\)的路径

那么我们发现,边\((u,v)\)的效果可以被\(u\)经\(s\)到\(v\)的路径所完全取代,且这条路径还可以产生好的效果。

所以边\((u,v)\)应当被删去

这样我们得到一个暴力算法,枚举每条边,然后对于每条边\(O(n)\)枚举另外一个点,直接判断即可

这样的复杂度是\(O(nm)\).

优化

考虑优化,拓扑排序对于一个点状压处理出这个点能够到达的点集\(F\)和能够到达这个点的点集\(G\),实际上一条边\((u,v)\)需要被保留等价于\(F_u \cup G_v = \emptyset\)

\(\text{bitset}\)维护一下,复杂度为\(O(\frac{nm}{\omega})\)

Code

点我看代码(✺ω✺)
#include <cstdio>
#include <iostream>
#include <map>
#include <string>
#include <bitset>
#define LL long long
#define RE register
#define IN inline
using namespace std;
IN int read() {
int res = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar());
for(; isdigit(ch); ch = getchar()) res = (res << 1) + (res << 3) + (ch ^ 48);
return res;
}
int cnt1, timer, tot, dfn[1010], low[1010], bel[1010], siz[1010], la[1010], stk[1010], top, ins[1010], c[1010], r[1010], head, tail, que[1010], n, ed[1010][1010], T, k, ans;
struct Edge {
int nxt, to;
}e[1000100];
inline void build(int u, int v) {e[++cnt1] = (Edge){la[u], v}, la[u] = cnt1;}
void Tarjan(int k) {
dfn[k] = low[k] = ++ timer, ins[stk[++top] = k] = 1;
for(int i = la[k], v; i; i = e[i].nxt) {
v = e[i].to;
if(!dfn[v]) Tarjan(v), low[k] = min(low[k], low[v]);
else if(ins[v]) low[k] = min(low[k], low[v]);
}
if(dfn[k] == low[k]) {
bel[k] = ++ tot, siz[tot] = 1, ins[k] = 0;
while(stk[top] ^ k) bel[stk[top]] = tot, ins[stk[top--]] = 0, ++ siz[tot]; -- top;
}
return;
}
bitset<1001> f[1001], g[1001];
void TopoSort() {
for(int i = 1; i <= tot; ++i)
for(int j = 1; j <= tot; ++j)
if(ed[i][j]) ++c[i], ++r[j];
head = tail = 0;
for(int i = 1; i <= tot; ++i) if(r[i] == 0) que[++tail] = i;
while(++head <= tail) {
int cur = que[head];
for(int i = 1; i <= tot; ++i)
if(ed[cur][i]) {
g[i] |= g[cur], g[i][cur] = 1;
if(!(--r[i])) que[++tail] = i;
}
}
head = tail = 0;
for(int i = 1; i <= tot; ++i) if(c[i] == 0) que[++tail] = i;
while(++head <= tail) {
int cur = que[head];
for(int i = 1; i <= tot; ++i)
if(ed[i][cur]) {
f[i] |= f[cur], f[i][cur] = 1;
if(!(--c[i])) que[++tail] = i;
}
}
return ;
}
map <string, int> mp;
char s[1000];
int main() {
T = read();
while(T --) {
n = read();
tot = ans = cnt1 = 0;
for(int i = 1; i <= n; ++i) la[i] = siz[i] = dfn[i] = low[i] = 0;
for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) ed[i][j] = 0;
mp.clear();
for(int i = 1, h; i <= n; ++i) {
scanf("%s%d",s,&k);
if(!mp[s]) mp[s] = ++tot;
h = mp[s];
for(;k;--k) {
scanf("%s",&s);
if(!mp[s]) mp[s] = ++ tot;
build(h, mp[s]);
}
}
tot = 0;
for(int i = 1; i <= n; ++i) if(!dfn[i]) Tarjan(i);
for(int i = 1; i <= tot; ++i) f[i].reset(), g[i].reset();
for(int i = 1; i <= n; ++i)
for(int j = la[i]; j; j = e[j].nxt)
if(bel[i] ^ bel[e[j].to]) ed[bel[i]][bel[e[j].to]] = 1;
for(int i = 1; i <= tot; ++i) if(siz[i] > 1) ans += siz[i];
TopoSort();
for(int i = 1; i <= tot; ++i)
for(int j = 1; j <= tot; ++j) {
if(ed[i][j] && i ^ j)
if((f[i] & g[j]).none()) ++ans;
}
printf("%d\n",ans);
}
}

GMOJ3284 [GDOI2013] 重构 题解的更多相关文章

  1. [Leetcode题解]2. 两数相加-链表遍历和重构

    1. 审题leetcode 02 add-two-numbers​ 我们先看一下题目,如下  : 链表的从前往后为数字的低位到高位,模拟加法手算过程,从前往后遍历即可, 注意每个数字0-9,进位要处理 ...

  2. HEOI2016 题解

    HEOI2016 题解 Q:为什么要在sdoi前做去年的heoi题 A:我省选药丸 http://cogs.pro/cogs/problem/index.php?key=heoi2016 D1T1 树 ...

  3. 题解 P5065 【[Ynoi2014]不归之人与望眼欲穿的人们】

    出现了一篇跑得炒鸡慢的题解! noteskey 无 fuck 说,好像就是整个数列分块然后合并区间...什么的吧 对于每块内部就是算一下前缀信息.后缀信息(就是以 第一个点/最后一个点 为一个边界,不 ...

  4. LibreOJ 题解汇总

    目录 #1. A + B Problem #2. Hello, World! #3. Copycat #4. Quine #7. Input Test #100. 矩阵乘法 #101. 最大流 #10 ...

  5. POJ - 题解sol[暂停更新]

    初期:一.基本算法: (1)枚举. (poj1753,poj2965) poj1753 话说我用高斯消元过了这题... poj2965 巧了,用高斯消元01矩阵更快(l o l). (2)贪心(poj ...

  6. 【题解】BZOJ 3065: 带插入区间K小值——替罪羊树套线段树

    题目传送门 题解 orz vfk的题解 3065: 带插入区间K小值 系列题解 一 二 三 四 惨 一开始用了一种空间常数很大的方法,每次重构的时候merge两颗线段树,然后无限RE(其实是MLE). ...

  7. 【题解】BZOJ 3600: 没有人的算术——替罪羊树、线段树

    题目传送门 题意 具体的自己去上面看吧...反正不是权限题. 简单来说,就是定义了一类新的数,每个数是0或者为 \((x_L, x_R)\) ,同时定义比较大小的方式为:非零数大于零,否则按字典序比较 ...

  8. 【题解】Jury Compromise(链表+DP)

    [题解]Jury Compromise(链表+DP) 传送门 题目大意 给你\(n\le 200\)个元素,一个元素有两个特征值,\(c_i\)和\(d_i\),\(c,d \in [0,20]\), ...

  9. ZROI 部分题目题解

    ZROI 部分题目题解 335 首先发现一个性质: 对于最短的边而言,所有点的路径如果经过了这条边,那么路径的权值就是这条边的边权(废话) 那么我们把最短的边拎出来,可以发现,博物馆确定时,每个点按照 ...

随机推荐

  1. BACnet IP转OPC UA网关

    BACnet是楼宇自动化和控制网络数据通信协议的缩写.它是为楼宇自动化网络开发的数据通信协议   根据1999年底互联网上楼宇自动化网络的信息,全球已有数百家国际知名制造商支持BACnet,包括楼宇自 ...

  2. 云图说丨初识华为云微服务引擎CSE

    摘要:微服务引擎(Cloud Service Engine,CSE),是用于微服务应用的云中间件,为用户提供注册发现.服务治理.配置管理等高性能和高韧性的企业级云服务能力 本文分享自华为云社区< ...

  3. GreatSQL重磅特性,InnoDB并行并行查询优化测试

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 1 ...

  4. 关于 java 的动态绑定机制

    关于 java 的动态绑定机制 聊一聊动态绑定机制, 相信看完这篇文章,你会对动态绑定机制有所了解. 网上大多一言概括: 当调用对象的时候,该方法会和该对象的内存地址/运行类型绑定. 当调用对象的属性 ...

  5. 【游记】CSP 2021 J2

    这次是第一次参加CSP的复赛,所以考的就很LJ. \(DAY-\infty\) 到 \(DAY-14\) 知道了自己苟过了初赛,像个SB一样. (我初赛66分,旁边那位63.5,cao着线过去的) \ ...

  6. 聊天机器人框架Rasa资源整理

      Rasa是一个主流的构建对话机器人的开源框架,它的优点是几乎覆盖了对话系统的所有功能,并且每个模块都有很好的可扩展性.参考文献收集了一些Rasa相关的开源项目和优质文章. 一.Rasa介绍 1.R ...

  7. java基础———打印三角形

    代码 public static void main(String[] args) { for (int i = 1; i <= 5; i++) { for (int j = 5; j > ...

  8. 【面试题】Vue中的$router 和 $route的区别

    Vue中的$router 和 $route的区别 点击视频讲解更加详细 this.$route:当前激活的路由的信息对象.每个对象都是局部的,可以获取当前路由的 path, name, params, ...

  9. uniapp+.net core 小程序获取手机号

    获取手机号 从基础库 2.21.2 开始,对获取手机号的接口进行了安全升级,以下是新版本接口使用指南.(旧版本接口目前可以继续使用,但建议开发者使用新版本接口,以增强小程序安全性) 因为需要用户主动触 ...

  10. k8s 网络持久化存储之StorageClass(如何一步步实现动态持久化存储)

    StorageClass的作用: 创建pv时,先要创建各种固定大小的PV,而这些PV都是手动创建的,当业务量上来时,需要创建很多的PV,过程非常麻烦. 而且开发人员在申请PVC资源时,还不一定有匹配条 ...