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);
}
}

最新文章

  1. web项目总结——通过jsp+servlet实现对oracle的增删改查功能
  2. 分布式日志2 用redis的队列写日志
  3. Ext.GridPanel 用法总结(一)—— Grid基本用法
  4. 【iOS】Quartz2D截屏
  5. View Properties [AX 2012]
  6. blazeDS集成spring的remote访问
  7. css3中的BFC,IFC,GFC和FFC(转载)
  8. 配置.NET程序中最大HTTP并发连接数(默认为2)
  9. 实现zbar扫描二维码的时候就把照片存储出来的办法
  10. 云脉表格识别开放SDK接入
  11. jsp include指令标签
  12. Post和Get差异
  13. C语言运算符运算顺序判断实例2
  14. k8s 创建资源的两种方式 - 每天5分钟玩转 Docker 容器技术(124)
  15. arduino扩展IO与M74HC595B芯片的使用,挪车电话提示牌的设计
  16. python之type函数
  17. sitecore 数字化营销-path funnel
  18. view 的用法
  19. Calendar获取当前年份、月份、日期
  20. ADB Fix error : insufficient permissions for device

热门文章

  1. 2499-springboot使用jar形式打包在linux上运行
  2. PHP正则替换函数收集
  3. YII自定义小部件
  4. you need to load the kernel first
  5. 最新MongoDB安装,学习笔记
  6. Camera类定义和实现
  7. react学习1-jsx语法注意点
  8. NC202475 树上子链
  9. silk-GUI图形界面开发一个词典
  10. react实战系列 —— React 中的表单和路由的原理