#2 CF521E & CF527E & CF516E

CF521E Cycling City

题目描述

点此看题

给定一张 \(n\) 个点 \(m\) 条边的无向简单图,问图中能否找到两个点,使得两个点之间至少有三条除端点之外点不交的路径。

\(n,m\leq 2\cdot 10^5\)

解法

我根本做不出这题,首先有一个奇妙的题目转化:两个点之间有三条点不交路径的充要条件是两点间有两个相交的环,它们显然可以互相推导。

转化成环问题之后不知道好做了多少!我们可以求原问题的 \(\tt dfs\) 树,那么问题就变成了找被非树边覆盖至少两次的树边,非树边可以暴力往上跳,因为每个点最多被跳两次所以时间复杂度 \(O(n)\)

现在的问题变成了给出一个合法的构造,假设这两条非树边是 \((a,b),(c,d)\)\(d\) 点比 \(b\) 点更深,这三条路径分别是:d->lcad->b->a->lcad->c->lca,从下图可以很好地看出来:

还有一些 \(\tt corner\ case\)(比如 \(a,c\) 祖先儿子关系、重合)就留给读者自己讨论了。

总结

分析答案的结构,把不同的图论意义之间做等价转化。

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
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,tot,f[M],vis[M],dep[M],fa[M];
int k,tmp[M],cx[M],cy[M];
struct edge{int v,next;}e[M<<1];
int lca(int u,int v)
{
	while(u!=v)
	{
		if(dep[u]>dep[v]) u=fa[u];
		else v=fa[v];
	}
	return u;
}
void fuck(int x,int y)
{
	for(int i=x;i!=y;i=fa[i])
		tmp[++k]=i;
	tmp[++k]=y;
}
void print()
{
	printf("%d ",k);
	for(int i=1;i<=k;i++)
		printf("%d ",tmp[i]);
	k=0;puts("");
}
void get(int a,int b,int c,int d)
{
	if(dep[b]>dep[d])
		swap(a,c),swap(b,d);
	int e=lca(a,c);
	puts("YES");
	//d->lca
	fuck(e,d);
	reverse(tmp+1,tmp+k+1);print();
	//d->b->a->lca
	fuck(d,b);fuck(a,e);print();
	//d->c->lca
	tmp[++k]=d;fuck(c,e);print();
	exit(0);
}
void dfs(int u)
{
	vis[u]=1;dep[u]=dep[fa[u]]+1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(!vis[v])
		{
			fa[v]=u;dfs(v);
			continue;
		}
		if(v==fa[u] || dep[v]>dep[u]) continue;
		for(int x=u;x!=v;x=fa[x])
		{
			if(cx[x])
				get(cx[x],cy[x],u,v);
			else
				cx[x]=u,cy[x]=v;
		}
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) dfs(i);
	puts("NO");
}

CF527E Data Center Drama

题目描述

点此看题

给定一个 \(n\) 个点 \(m\) 条边的联通无向图,你需要加入尽可能少的边,然后给所有边定向,使得每个点的出入度都是偶数,边既可以是自环也可以是重边。

\(n\leq 10^5,m\leq 2\cdot 10^5\)

解法

感觉就是一道构造题,我们先找必要条件:在无向图的意义下,每个点的度数都要是偶数(因为每一条边要不然贡献入度要不然贡献出度),边的总数是偶数。

我们根据必要条件来构造,那么显然需要把每个点的度数补成偶数。我们先任意把两个奇数点配对然后连边,如果最后总边数为奇数那么我们补一个自环。

考虑现在得到的图每个点都是偶数,这个图一定是存在欧拉回路的。我们考虑用欧拉回路求出一个包含所有边的大环,然后给环上的边依次正反定向:a->b<-c->d<-e->f<-a,那么显然最后每个点的入度和出度都是偶数。

小细节:本题由于存在自环不能用直接选择一条边 \(\tt dfs\) 的方法,而应用循环 \(\tt dfs\),在回溯的时候把当前边加入欧拉回路中(这应该是目前最不易出错的欧拉回路写法)

总结

现在我遇到的欧拉回路模型:给边定向使得入度等于出度;求出一个包含所有边的大环。边定向问题可以考虑欧拉回路

#include <cstdio>
const int M = 100005; 
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,cnt,tot,f[M],cur[M],a[M],d[M];
int vis[M<<3];
struct edge{int v,id,next;}e[M<<3];
void add(int u,int v)
{
	d[u]++;d[v]++;cnt++;
	e[++tot]=edge{v,cnt,f[u]},f[u]=tot;
	e[++tot]=edge{u,cnt,f[v]},f[v]=tot;
}
void dfs(int u)
{
	for(int &i=f[u];i;i=e[i].next)
	{
		if(vis[e[i].id]) continue;
		vis[e[i].id]=1;
		int v=e[i].v;dfs(v);cnt++;
		if(cnt&1) printf("%d %d\n",u,v);
		else printf("%d %d\n",v,u);
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
		add(read(),read());
	for(int i=1;i<=n;i++)
		if(d[i]&1) a[++k]=i;
	for(int i=1;i<=k;i+=2)
		add(a[i],a[i+1]);
	if(cnt&1) add(1,1);
	for(int i=1;i<=n;i++) cur[i]=f[i];
	printf("%d\n",cnt);
	cnt=0;dfs(1);
}

CF516E Drazil and His Happy Friends

题目描述

点此看题

还说如果这题魔改一下题面就会很有意思,以后再说。

解法

首先考虑如何判断有解,根据我微薄的数论知识,令 \(d=\gcd(n,m)\),我们考虑把模 \(d\) 同余的男生和女生都放在一组,那么只有组内的快乐才会传递,那么有解的充要条件是每组都至少有一个人快乐。

如果有解那么 \(d\leq 2\cdot 10^5\),我们可以尝试把每一组单独讨论了。每一组的结束时间可以转化成每个人快乐时间的最大值,发现还是难以处理。使用拆分法,原问题是男女生混合快乐的过程(?),那么为了简化问题我们把男女生拆开,然后把快乐的传递都放在女生处(我只是觉得女孩子之间就是好啊),这样男女生就可以分别计算了。

具体来说,我们考虑某个男生 \(j\bmod n\) 在时刻 \(j\) 因为女生 \(j\bmod m\) 变快乐了,那么在 \(n\) 个单位时间后他可以让女生 \((j+n)\bmod m\) 变快乐,这可以理解成女生快乐的直接传递,我们把这个关系建成图:

  • 女生 \(j\) 向女生 \((j+n)\bmod m\) 连一条长度为 \(n\) 的边,表示快乐的传递。
  • 如果女生 \(i\) 初始就是快乐的,那么由源点向 \(i\) 连一条长度为 \(i\) 的边,表示从时刻 \(i\) 开始传递快乐。
  • 如果男生 \(i\) 初始就是快乐的,那么由源点向 \(i\bmod m\) 连一条长度为 \(i\) 的边。

我们从源点开始跑最短路就可以求出每个人的快乐时间,然后把初始不快乐女生的最短路取最大值就得到了答案。

但是直接跑最短路显然会超时,我们考虑第二、三类边的数量是有限的,可以由第一类边来重构这个环。新环上相邻两个点原来的距离是 \(n\),在新环上我们只保留二、三类边所连接的点,然后对于环上每一段我们计算最后一个点的到达时间,取最大值即可,时间复杂度 \(O(d\log d)\)

怎么对环进行重标号呢?设点 \(x\) 是由 \(0\)\(i\) 步达到的,那么 \(in=x\bmod m\),所以 \(i=x\cdot n^{-1}\bmod m\),我们用 \(\tt exgcd\) 求出逆元就可以重构环了。

总结

拆分法的使用十分精妙,本题是把两个东西之间的影响拆开,都放在一个东西上,可见把限制集中常常是好事。

对于巨大范围的东西常常需要观察结构,比如本题的第一类边是环的结构,就可以从这个结构出发解题了。

//Why you always in a mood? 
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define pb push_back
#define int long long
#define Morisummer {puts("-1");return 0;}
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,ans,s[M],a[M],p[M];
vector<int> A[M],B[M]; 
int exgcd(int a,int b,int &x,int &y)
{
	if(!b) {x=1;y=0;return a;}
	int d=exgcd(b,a%b,y,x);
	y-=(a/b)*x;return d;
}
int work(int m,int n,int x,vector<int> &G,vector<int> &B)
{
	if(G.size()==m) return -1;//needn't to operate
	int cnt=0,dis=1e18,ans=0;
	for(int i:G) s[++cnt]=i*x%m,a[cnt]=i,p[cnt]=cnt;
	for(int i:B) s[++cnt]=i*x%m,a[cnt]=i,p[cnt]=cnt;
	sort(p+1,p+1+cnt,[](int i,int j)
	{return s[i]==s[j]?i<j:s[i]<s[j];});//if s[i]==s[j],place boy back
	s[p[0]=0]=s[p[cnt]]-m;s[p[cnt+1]=cnt+1]=s[p[1]]+m;//easy to calc dis
	for(int i=1;i<=cnt;i++)//distance : under the circumstance of cirlces
		dis=min(dis+n*(s[p[i]]-s[p[i-1]]),a[p[i]]);
	for(int i=1;i<=cnt;i++)
	{
		dis=min(dis+n*(s[p[i]]-s[p[i-1]]),a[p[i]]);
		if(s[p[i]]==s[p[i+1]]) continue;//not need to be considered
		if(s[p[i]]+1==s[p[i+1]] && p[i]<=G.size()) continue;//girls born to be happy
		ans=max(ans,dis+n*(s[p[i+1]]-s[p[i]]-1));//the last point of [s[p[i]],s[p[i+1]])
	}
	return ans;
}
signed main()
{
	n=read();m=read();int x=0,y=0,d=0;
	if((d=exgcd(n,m,x,y))>200000) Morisummer
	for(int i=0,t,k=read();i<k;i++)
		t=read(),A[t%d].pb(t/d);
	for(int i=0,t,k=read();i<k;i++)
		t=read(),B[t%d].pb(t/d);
	x=(x%m+m)%m;y=(y%n+n)%n;
	for(int i=0;i<d;i++)
	{
		if(A[i].empty() && B[i].empty()) Morisummer
		ans=max(ans,work(m/d,n/d,x,B[i],A[i])*d+i);
		ans=max(ans,work(n/d,m/d,y,A[i],B[i])*d+i);
	}
	printf("%lld\n",ans);
}
posted @ 2022-01-26 21:06  C202044zxy  阅读(157)  评论(2编辑  收藏  举报