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

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

一道思维题,考查点竟不是一些奇怪的操作

题意

函数 \(f(u,G)\) 的意义无非是:

\([1,u]\) 这段范围内的点 \(x\) (枚举到 \(u\) 的时候把自己就删了),满足 \(u\)\(x\) 双连通的 \(x\) 的个数。

要求求出 \(\sum f(u,G)\) 和删去 \(1-i\) 后的边形成图的函数值,记作 \(h(G')\)

我语文不好,自己品吧

题解

考场上发现了这个结论,没有思考对不对,其实手玩就可以玩出来:

两个点 \(u,v\) 对答案有贡献,当且仅当在图中存在 \(u->v\)\(v->u\) 不经过 \([1,v)\) 的点的路径

证明:

如果存在 \(u->x->v\)\(v->u\) 的路径 (\(x<v\)),那么 \(u\)\(x\) 一定双连通,那么一定会在枚举到 \(x\) 时把此路断掉。

证毕。

这是一个非常常见的套路!!!

删边不好删,于是可以把时间轴逆过来进行加边操作

由于加边后点对数量不会减少,我们可以利用差分思想求解。

所以我们只需要依次单调递减枚举中间点 \(k\),按上面的规律进行统计即可记录下每一对点第一次对答案做出贡献的时间(即 \(f[i][j]\ and\ f[j][i]\)

最后求一个差分的前缀和即可。

显而易见的,\(ans[m+1]=n\) (边都删完了)


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 10,maxm = 2e5 + 3;
int dff[maxm],ans[maxm],f[maxn][maxn];//两个点是什么时候贴贴的
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		f[u][v]=i;
	}
	for(int k=n;k>=0;k--){
		for(int i=k+1;i<=n;i++)dff[min(f[k][i],f[i][k])]++;
		/*
		这里是根据上一轮(k+1)的数据更新新扩展点 k 的答案,采用了差分思想,是全算法的核心所在 
		*/ 
		for(int i=1;i<=n;i++){
			if(!f[i][k])continue;//压根不联通 
			if(i>k)for(int j=1;j<k;j++)f[i][j]=max(f[i][j],min(f[i][k],f[k][j]));//没有意义进行所有扩展,进行部分扩展即可 
			else for(int j=1;j<=n;j++)f[i][j]=max(f[i][j],min(f[i][k],f[k][j]));//至于max 和 min的意义自己领会(往问题的本源想) 
		}
		printf("k: %d\n",k);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				printf("f[%d][%d]:%d ",i,j,f[i][j]);
			}
			puts("");
		}
	}
	int tot=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++)if(f[i][j]&&f[j][i])tot++;
	}
	printf("tot: %d\n",tot);//调试原图的总点对 
	ans[m+1]=n;
	for(int i=m;i>=1;i--)ans[i]=ans[i+1]+dff[i];
	for(int i=1;i<=m+1;i++)printf("%d ",ans[i]);
	puts("");
	return 0;
}
posted @ 2021-08-12 17:11  ¶凉笙  阅读(108)  评论(0)    收藏  举报