• [2019 集训队互测 Day4] 绝目编诗


    一、题目

    点此看题

    二、解法

    谢谢这个大佬的博客,讲得真的挺好的。

    做的时候脑抽了,连暴搜都不会,我们先来考察一种有前途的暴搜。

    首先枚举起点,然后尝试搜出一条到起点的回路(就是最暴力的方法,枚举下一个点然后回溯)。搜到每一个点的时候判断,如果它没有构成环的可能(即不通过在搜索栈中的节点到达起点),那么直接退出。

    分析一下这种方法的时间复杂度,首先根据抽屉原理,环的个数不超过 \(n\) 个,最坏的情况是 \(3\sim n\) 各分布一个。考虑一个长度为 \(i\) 个环会被搜到 \(2i\) 次,每次搜到都需要走 \(i\) 个点,所以是 \(\sum_{i=3}^n2i^2=O(n^3)\);此时再乘上检查的复杂度,那么我们得到了一个稳定 \(O(n^4)\) 的算法。稍加随机化和掐表随便过前四个包,甚至可能直接过掉本题

    有一个神仙结论是 \(m>n+2\sqrt n\) 无解,考虑随机删 \(\sqrt n\) 条边期望剩下的环个数:

    \[\sum_{i=3}^n(1-\frac{1}{\sqrt n})^i<\frac{1}{1-(1-\frac{1}{\sqrt n})}=\sqrt n \]

    那么一定存在一种删边使得剩下环的个数 \(\leq \sqrt n\),我们再用 \(\sqrt n\) 次就一定能删完所有环。换句话说,我们用 \(2\sqrt n\) 次可以让这个图至多剩下 \(n-1\) 条边,那么原图的边数一定不超过 \(n+2\sqrt n\)

    考虑原图的一棵 \(\tt dfs\) 树,我们把非树边标记成关键点,然后建立关键点的虚树,再添加上非树边就得到了一个点数边数都是 \(O(\sqrt n)\) 级别的图。在这个图上找环就等价于在原图上找环,一开始介绍的 \(O(n^4)\) 方法是可以支持带权边找环的,直接套用它,时间复杂度 \(O(n^2)\)

    #include <cstdio>
    #include <vector>
    #include <cstdlib>
    #include <iostream>
    #include <cmath>
    using namespace std;
    const int M = 10005;
    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,k,tot=1,f[M],zz[M];vector<int> g[M];
    int p[M],vis[M],dep[M],dis[M],len[M],cnt[M];
    struct edge{int v,c,next;}e[M<<1];
    void add(int u,int v,int c)
    {
    	e[++tot]=edge{v,c,f[u]},f[u]=tot;
    	e[++tot]=edge{u,c,f[v]},f[v]=tot;
    }
    void dfs(int u,int fa)
    {
    	int son=0;
    	dep[u]=dep[fa]+1;zz[u]=fa;
    	for(int v:g[u])
    	{
    		if(v==fa) continue;
    		if(dep[v])
    		{
    			if(dep[u]<dep[v]) continue;
    			if(!p[u]) p[u]=u;
    			if(!p[v]) p[v]=v;
    			add(p[u],p[v],1);
    			continue;
    		}
    		dfs(v,u);
    		if(p[v]) son=son?-1:p[v];
    	}
    	if(!p[u] && son<0) p[u]=u;
    	if(p[u])
    	{
    		for(int v:g[u]) if(zz[v]==u && p[v])
    			add(p[u],p[v],dep[p[v]]-dep[p[u]]);
    	}
    	else p[u]=son;
    }
    int check(int u,int nw)
    {
    	if(!dep[u]) return 1;vis[u]=nw;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(vis[v]==nw || dep[v]>0) continue;
    		if(check(v,nw)) return 1;
    	}
    	return 0;
    }
    void work(int u,int fa)
    {
    	if(!check(u,++k)) return ;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v,c=e[i].c,r=c+dis[u];
    		if(i==fa) continue;
    		if(!dep[v])//find root
    		{
    			if(!len[r]) len[r]=dep[u]+1;
    			if(len[r]!=dep[u]+1) {puts("Yes");exit(0);}
    			cnt[r]++;
    			if(cnt[r]>2*len[r]) {puts("Yes");exit(0);}
    		}
    		if(dep[v]>=0) continue;
    		dep[v]=dep[u]+1;dis[v]=r;
    		work(v,i^1);dep[v]=-1;
    	}
    }
    signed main()
    {
    	n=read();m=read();
    	if(m>n+2*sqrt(n)) {puts("Yes");return 0;}
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	for(int i=1;i<=n;i++)
    		if(!dep[i]) dfs(i,0);
    	for(int i=1;i<=n;i++) dep[i]=-1;
    	for(int i=1;i<=n;i++) if(p[i]==i)
    		dep[i]=dis[i]=0,work(i,0),dep[i]=-1;
    	puts("No");
    }
    
  • 相关阅读:
    [背包]JZOJ 3232 【佛山市选2013】排列
    内核空间、用户空间、虚拟地址
    进程与线程的概念
    Python中字符串颜色
    socket编程
    模块与包
    常用模块
    面向对象进阶
    面向对象编程
    函数式编程
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16368102.html
Copyright © 2020-2023  润新知