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

· · 题解

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

更好的阅读体验

题意

给定一张n个点m条边的有向图G,定义函数f(u,G)的值为下列操作的返回值:

cnt=0,G'=G,然后按1n的顺序枚举点v,如果G'vu能双向到达,那么cnt++,并删去结点v,最后返回cnt

定义h(G)=\sum_{i=1}^n f(i,G),并定义G_i为图G删除边1\cdots i后的图,求出h(G_{0\cdots n})

## 分析 定义$g_{x,y}(x\leqslant y)$为$x$经过$[x,n]$的点能否到达$y$,同样设$g_{x,y}(x>y)$为$x$经过$[y,n]$的点能否到达$y$,那么对于图$G$,求的其实就是$\sum_{i\ne j}g_{i,j}\text{and}\ g_{j,i}$。 发现如果图改变需要重新处理值,这肯定会超时,因此将$g_{x,y}$中是否能到达改为删除最多的边使得能让其到达的边数(无法到达则为$0$)。 那么如果我们处理出$g_{x,y}$,我们可以求出答案的差分数组,然后前缀和就好了。 考虑类似Floyd的做法,从大到小枚举中转点$k$(这样做可以让循环到$k$时经过的点全部大于等于$k$,便于统计答案),然后对路径进行合并。 具体地,我们对于$\leqslant k$且可以经过$>k$的结点到达$k$(其实就是判断当时的$g_{i,k}$是否非零)的点$i$,枚举终点$j$,那么会存在一条$i\rightarrow k\rightarrow j$的路径,且不难发现这条路径删除的边数为$\min\{g_{i,k},g_{k,j}\}$,用这个权值更新$g_{i,j}$就好了。 同理对于$>k$且可以经过$>k$的结点到达$k$的点$i$,枚举终点$j$(这里的$j$要小于$k$,否则答案会算重。但其实算重也没关系,因为这一部分的答案已经在之前更新过了),按上面同样的方式更新答案就好了。 时间复杂度:$O(n^3+m)$。 ## 代码 ``` #include<stdio.h> const int maxn=1005,maxm=200005; int n,m; int g[maxn][maxn],sum[maxm],ans[maxm]; inline int min(int a,int b){ return a<b? a:b; } inline int max(int a,int b){ return a>b? a:b; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) g[i][i]=m+1; for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); g[x][y]=i; } for(int k=n;k>=1;k--){ for(int i=k;i<=n;i++) sum[min(g[i][k],g[k][i])]++; for(int i=1;i<=k;i++) if(g[i][k]) for(int j=1;j<=n;j++) g[i][j]=max(g[i][j],min(g[i][k],g[k][j])); for(int i=k+1;i<=n;i++) if(g[i][k]) for(int j=1;j<k;j++) g[i][j]=max(g[i][j],min(g[i][k],g[k][j])); } for(int i=m;i>=1;i--) sum[i]+=sum[i+1]; for(int i=1;i<=m+1;i++) printf("%d%c",sum[i],i==m+1? '\n':' '); return 0; } ``` 省选联考A卷全部题解可见:[2021省选联考A卷解题报告](https://zybuluo.com/xiaoziyao/note/1791034)