[省选联考 2021 A/B 卷] 图函数

时间:2021-07-17
本文章向大家介绍[省选联考 2021 A/B 卷] 图函数,主要包括[省选联考 2021 A/B 卷] 图函数使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、题目

点此看题

开始接受\(...\)痛苦不堪的回忆。

二、解法

你看它不用算具体的东西,只用算一个总和,这不用贡献法用什么?

考虑 \(v\) 的贡献,也就是保留 \([v,n]\) 的点和有关边时,和它能互通 \(u\) 点的个数。前 \([1,v)\) 不用考虑是因为如果和 \(u\) 能互通就会删除,如果不互通那么和 \(u\) 不在一个强连通块内,对 \(u,v\) 是否能互通没有任何影响。

枚举点 \(v\),然后跑 \(\tt tarjan\),单次可以做到 \(O(nm)\) 的复杂度。

先固定点 \(v\),考虑删除一个边的前缀的影响,把删边变成加边是老套路了,我们逆序来做。\(\tt tarjan\) 不好维护但考虑到有一个点是定点,所以建出正反图,然后动态 \(\tt bfs\),如果加入的这条边两个端点都访问过就没用,如果终点没有访问过就以他开始 \(\tt bfs\),每次把 \(\tt bfs\) 到的边删掉,如果两个端点都没访问过就加入图中。每条边只会被删除一次,所以时间复杂度 \(O(nm)\)


还有一种方法是考虑点对 \((u,v)\) 的贡献,也就是保留 \([v,n]\) 的点和边时 \(u,v\) 能互通。因为本题边越多 \(u,v\) 更容易互通,所以我们给每个边一个时间(第 \(i\) 条边时间为 \(i\)),可以考虑求出 \(u,v\) 互通的最小瓶颈边最大的路径,那么可知这会贡献给答案序列的一个前缀。

有向图的瓶颈边问题可以考虑 \(\tt floyd\),设 \(f[i][j]\) 表示 \(i\)\(j\) 的最小瓶颈边的最大值,那么考虑枚举中转点 \(k\),有一个限制是 \(k\geq\min(i,j)\),因为图只能保留这些点,在内层循环的时候注意一下即可。

还有一个细节是 \(k\) 要倒序枚举,首先考虑 \(\tt floyd\) 的原理:对于每一条可能的路径,我们通过枚举中转点能把这些点一个一个拼起来,这相当于枚举了所有情况。但是这道题的路径可能是 small->big->middle->big->small,如果先枚举了small就会导致路径拼不起来,所以要先枚举big

时间复杂度 \(O(n^3)\),真 \(\tt tm\) 怎么做都可以,但是考试时就是不会。

三、总结

当不用求出具体值,只用求总和时,考虑贡献法。

尝试多种翻译方式,比如这道题我一开始翻译的是 \((u,v)\) 强联通,但是因为 \(\tt tarjan\) 不好动态做所以卡了。但是如果翻译成 \((u,v)\) 互通就利于后面想到动态 \(\tt bfs\) 的方法。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,f[M][M],ans[200005];
signed main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        f[u][v]=i;
    }
    for(int k=n;k>=1;k--)
    {
        for(int i=1;i<=n;i++)
        {
            if(!f[i][k]) continue;
            int t=f[i][k],up=(i>k)?(k-1):n;
            for(int j=1;j<=up;j++)
                f[i][j]=max(f[i][j],min(t,f[k][j]));
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            ans[min(f[i][j],f[j][i])]++;
    ans[m+1]=n;
    for(int i=m;i>=1;i--) ans[i]+=ans[i+1];
    for(int i=1;i<=m+1;i++) printf("%d ",ans[i]);
}

原文地址:https://www.cnblogs.com/C202044zxy/p/15024721.html