zoukankan      html  css  js  c++  java
  • bzoj4238-Loj2881-JOISC2014 电压【树上差分】

    题目链接

    题目解析

    如果对每一条边都尝试染色的话,复杂度显然不可过。

    考虑从宏观上大局考虑。(这句话怎么这么别扭,是我语文太菜了

    如果图中有奇环,那么奇环上的边必须被选,否则如果选了其它的边,奇环上的边就一定有一条左右端点颜色一样。

    同样考虑偶环,我们发现偶环上的边必须不被选,否则如果选了偶环上的边,那么偶环上一定有一条边左右端点颜色一样。

    总结一下,就是:
    如果没有奇环,那么偶环以外的边可以随便选。

    如果有奇环,那么能选的边就是所有奇环上的边的交集,并且不在偶环上。

    不过,在偶环上的边一定不在奇环上,因为如果一条边既在偶环上,又在奇环上,那通过这条边相连的奇环和偶环可以组成一个更大的奇环(可以画图看一下),而这条边不在这个更大的奇环上。(当然,我的做法判不了这个更大的奇环,所以需要判偶环)

    算了,还是画个图:(这个图可能要先看一下下面的具体做法再来看

    (可以看到从树上下下来长这样,可以更直观地看到(2-3)边一定不在大奇环(1-2-5-4-3-1)

    可以用(dfs)序解决这个问题,如果搜到一条返祖边就说明成环了,而我们可以通过(Δdep)判断是偶环还是奇环,然后树上差分就可以算出这条边在多少个奇环/偶环上。

    最后统计答案找到所有奇环覆盖数(==)奇环总数,并且不在偶环上的边的总数。

    注意一下,我们在算答案的时候是没有考虑返祖边的,因为如果有(2)个以上的奇环,那么这两条返祖边都不在一起,肯定不在交集里面。但是,如果只有一个奇环的话,这条返祖边是可行的,所以答案要(+1)

    最后再注意一下程序写法,是差分边,不是差分点,注意一下细节。


    ►Code View

    #include<cstdio>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    using namespace std;
    #define N 100005
    #define M 200005
    #define MOD 998244353
    #define INF 0x3f3f3f3f
    int rd()
    {
    	int x=0,f=1;char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
    	return f*x;
    }
    struct node{
    	int nxt,v;
    }e[M<<1];
    int hd[N],cnt=1;
    void add(int u,int v)
    {
    	e[++cnt].nxt=hd[u],e[cnt].v=v;
    	hd[u]=cnt;
    }
    
    int n,m;
    bool vis[N],rt[N];
    int dep[N],sum[N][2]/*差分数组 标记这个点头上的那条边 注意是边*/,tot/*奇环个数*/;
    int f[N];//记录这个点头上的边的编号 
    void dfs(int u)
    {
    	vis[u]=1;
    	for(int i=hd[u];i;i=e[i].nxt)
    	{
    		int v=e[i].v;
    		//if(v==fa) continue;
    		if(i==f[u]||(i^1)==f[u]) continue;
    		if(vis[v])//返祖边
    		{
    			if(dep[v]>dep[u]) continue;//走到了返祖边的反向边 
    			int d=(dep[u]-dep[v])&1;
    			sum[u][d]++,sum[v][d]--;
    			if(!d) tot++;
    		} 
    		else
    		{
    			f[v]=i;
    			dep[v]=dep[u]+1;
    			dfs(v);
    			sum[u][0]+=sum[v][0],sum[u][1]+=sum[v][1];//差分合并 
    		}
    	}
    }
    int main()
    {
    	n=rd(),m=rd();
    	for(int i=1;i<=m;i++)
    	{
    		int u=rd(),v=rd();
    		add(u,v);
    		add(v,u);
    	}
    	for(int i=1;i<=n;i++)
    		if(!vis[i]) 
    		{
    			rt[i]=1;
    			dfs(i);
    		}
    	int ans=0;
    	//printf("%d
    ",tot);
    	//for(int i=1;i<=n;i++)
    	//	printf("%d
    ",sum[i][1]);
    	for(int i=1;i<=n;i++)
    		if(sum[i][0]==tot&&!sum[i][1]&&!rt[i])//在所有奇环上 不在任何偶环上 并且不是根(根头顶上没有边
    			ans++;
    	if(tot==1) ans++;//只有一个奇环 那么那条返祖边也要算上 
    	printf("%d
    ",ans); 
    	return 0;
    }
    /*
    因为这道题是按边算 可能会有重边 所以只能写前向星 按边来
    主要是v==fa那里的判断 不能判断是不是父节点 而要判是不是当初来的那条边或它的反向边 
    样例2就可以帮助理解这种情况 
    */
    
  • 相关阅读:
    c#中开发ActiveX的学习笔记
    [转]李战大师悟透delphi 第七章 组织你的模块
    在网页中实现QQ的屏幕截图功能
    vs.net的调试小技巧之#define debug(适合新手)
    socket中的byte消息格式设计
    [转]李战大师悟透delphi第五章 包
    [转]李战大师悟透delphi 第九章 多层体系结构
    [转]李战大师悟透delphi第一章 delphi的原子世界
    重温delphi之控制台程序:Hello World!
    silverlight中的socket编程注意事项
  • 原文地址:https://www.cnblogs.com/lyttt/p/14068976.html
Copyright © 2011-2022 走看看