洛谷P6134 [JSOI2015]最小表示 题解
题目链接:P6134 [JSOI2015]最小表示
题意:
对于一个 \(N\) 个点(每个点从 \(1\) 到 \(N\) 编号),\(M\) 条边的有向图,cxy 发现,如果从图中删去一些边,那么原图的连通性会发生改变;而也有一些边,删去之后图的连通性并不会发生改变。
cxy 想知道,如果想要使得原图任意两点的连通性保持不变,我们最多能删掉多少条边呢?
为了简化一下大家的工作量,这次 cxy 保证他给定的有向图一定是一个有向无环图(cxy:大家都知道对于给任意有向图的问题,最后都能转化为有向无环图上的问题,所以就干脆简化一下大家的工作)。
输入格式:
第一行包含两个正整数 \(N\) 和 \(M\)。
接下来 \(M\) 行,每行包含两个 \(1\) 到 \(N\) 之间的正整数 \(x_i\) 和 \(y_i\),表示图中存在一条从 \(x_i\) 到 \(y_i\) 的有向边。
输入数据保证,任意两点间只会有至多一条边存在。
输出格式:
输出一行包含一个整数,表示cxy最多可以删掉的边数。
数据范围:
对于 \(100\%\) 的数据,\(1 \leq N\leq 3\times 10^4\),\(0 \leq M\leq 10^5\)。
不难发现每对节点保留其最长的路径一定可以得到最优解。
证明:
感性理解的话,是因为保留这些边的过程最大化了边的利用价值。
考虑原图的一个拓扑序,显然它构成一棵树,且边均由子结点指向父结点
当然拓扑序不能反映原图的连通性,实际上这棵树上还有一些“返祖边”
这里“返祖边”类似于dfs树的返祖边,由某个结点指向它的某个祖先或祖先的兄弟。
如果 \(u\) 为叶子结点,显然仅留“返祖边”是最优的。
如果 \(u\) 不为叶子结点,我们首先会尝试让 \(u\) 的儿子连上 \(u\) ,然后尝试 \(u\) 的孙子...
不难发现这样一定是优的,因为连深度较大的结点只会让答案增大。
至此,我们证明了算法的正确性。\(\square\)
考虑用 \(\mathtt{bitset}\) 维护结点的连通性。
设 \(u\) 向 \(v\) 连了一条有向边。
- 我们用
link[u][v]
表示 \(u\) 与 \(v\) 连通 - 因为 \(v\) 能到达的结点 \(u\) 也能到达,因此我们可以直接
link[u] |= link[v]
时间复杂度 \(\mathcal{O}\left(\frac{n^2\log n}{32}\right)\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(3e4+15))
#define M ((int)(1e5+15))
bitset<N> link[N];
int n,m,pos=1,head[N],to[N],in[N],id[N],dfn[N];
struct Edge{int u,v,next;} e[M];
void addEdge(int u,int v) { e[++pos] = {u,v,head[u]}; head[u] = pos; }
int topo()
{
int res=0,tim=0; queue<int> q;
for(int i=1; i<=n; i++) if(!in[i]) q.push(i);
while(!q.empty())
{
int u = q.front(); q.pop();
id[++tim] = u; dfn[u] = tim;
for(int i=head[u]; i; i=e[i].next)
if(!(--in[e[i].v])) q.push(e[i].v);
}
for(int k=n; k>=1; k--)
{
int u = id[k], cnt=0; link[u][u] = 1;
for(int i=head[u]; i; i=e[i].next)
to[++cnt] = e[i].v;
sort(to+1, to+1+cnt, [](int a,int b) { return dfn[a] < dfn[b]; });
for(int i=1; i<=cnt; i++)
if(link[u][to[i]]) ++res;
else link[u] |= link[to[i]];
}
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m;
for(int i=1,u,v; i<=m; i++)
{
cin >> u >> v;
addEdge(u,v); ++in[v];
}
cout << topo() << '\n';
return 0;
}
参考文献:
[1] https://www.luogu.com.cn/blog/jasony/p6134-jsoi2015-zui-xiao-biao-shi