• AT1226/「JOISC 2014 Day3」电压 题解


    题意

    \(~~~~\) 给定了一张图,求该图有多少条边满足删掉它后图变为二分图,且删去的边的两点在二分图中要在同一部中。

    题解

    \(~~~~\) 二分图的判定条件是图中没有 奇环,因此合法边需要满足删去它后图上没有奇环。

    \(~~~~\) 那么我们求出所有奇环的边的交即可,然后还要使得这些边不能在偶环上,因为若一个奇环与偶环有交那么它们即使删去相交的边也还是有一个更大的奇环;或者原图没有奇环,即原图就是二分图,那么删去这条边后二分图的染色方案仍和原来一样,此时删去的边的两端分属两部,这也是不合法的,这里建议自己画图看一下。

    \(~~~~\) 那我们如何求出每条边在多少奇环和偶环上?

    \(~~~~\) 考虑建出原图的DFS树,然后考虑每条返祖边的贡献

    • 若环上只有一条返祖边:

      • 若该环为奇环,这个环上所有树边都是在奇环中的。(由于有多个返祖边)
      • 若该环为偶环,那么同上。
    • 若环上有2+返祖边,显然两条返祖边有两个小环,且两条返祖边的点经过的树边一定有交,否则就会有一条树边被经过两次,这样就没有简单环了。而两条返祖边形成的大环可以再与其他返祖边组成环,因此我们证两条返祖边的情况然后进行归纳即可。

      • 若两个环分别为奇环和偶环,那么它们形成了一个大奇环,此时大奇环上的树边全部少统计了一遍,但我们也没有数这个奇环,所以不统计小奇环上的树边没有影响,而偶环上的即使统计过显然也不会成为答案,因此对答案没有影响。
      • 若两个都是奇环,那么它们的交是不在形成的大偶环上的,而其他在大偶环上的边至少不在一个奇环上,本身就不会被统计入答案里面,因此对答案没有影响。
      • 若两个都是偶环,那么显然本身都不会被计入答案,自然形成大偶环也对答案没有影响。

      综上,做题当时少考虑这种情况但对答案没有影响。.

    \(~~~~\) 然后只有一条返祖边的情况边差分做即可,一遍DFS即可。

    \(~~~~\) 注意只有一个奇环时那条返祖边也是合法的。

    \(~~~~\) 时间复杂度 \(\mathcal{O(n)}\)

    代码

    查看代码
    #include <map>
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    #define mp(a,b) make_pair(a,b)
    using namespace std;
    bool vis[100005];
    int fa[1000005],cnt[100005][2],dep[100005],jh;
    vector < pair<int,int> >G[100005];
    void dfs(int u,int d)
    {
    	dep[u]=d;
    	vis[u]=true;
    	for(int i=0;i<G[u].size();i++)
    	{
    		int v=G[u][i].first,w=G[u][i].second;
    		if(w==fa[u]) continue;
    		if(vis[v])
    		{
    			if(dep[v]>dep[u]) continue;
    			int k=(dep[u]-dep[v])&1;
    			if(k==0) jh++; 
    			cnt[u][k]++;
    			cnt[v][k]--;
    		}
    		else
    		{
    			fa[v]=w;
    			dfs(v,d+1);
    			cnt[u][0]+=cnt[v][0];cnt[u][1]+=cnt[v][1];
    		}
    	}
    } 
    int main() {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1,u,v;i<=m;i++)
    	{
    		scanf("%d %d",&u,&v);
    		G[u].push_back(mp(v,i));
    		G[v].push_back(mp(u,i));
    	}
    	for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0);
    	int Ans=0;
    //	for(int i=1;i<=n;i++) printf("odd=%d even=%d\n",cnt[i][0],cnt[i][1]);
    	for(int i=1;i<=n;i++)
    	{
    		if(fa[i]&&cnt[i][0]==jh&&!cnt[i][1]) Ans++;
    	}
    	printf("%d\n",Ans+(jh==1));//AT上记得换行
    	return 0;
    }
    
  • 相关阅读:
    socket网络编程
    Oracle 分区表 总结
    修改配置文件后不重启程序即时生效
    vue 计算属性
    Vue属性绑定指令 vbind
    vue虚拟dom
    vueslot插槽
    将博客搬至CSDN
    打造山寨手机中的App Store斯凯MiniJ(mrp)诞生记
    【教程】查看某个程序的占用的端口
  • 原文地址:https://www.cnblogs.com/Azazel/p/14578167.html
Copyright © 2020-2023  润新知