LOJ3057[HNOI2019]校园旅行【dp+优化建图】

20 篇文章 0 订阅

LOJ3057


updated 11.7
发现在UOJ上TLE。改进了代码实现。
生成树是可以直接做的,减少了常数,这样每一颗树最多连一条自环,加一个标记即可。


SOL

  • 30pts

从构造路径的角度思考,难以入手。总点数能实现 O ( n 2 ) O(n^2) O(n2)算法,转换思路,从中间向两边接点,构造回文串。

f [ i ] [ j ] f[i][j] f[i][j]表示 i , j i,j i,j点对能否作为回文路径的两端。故枚举 i , j i,j i,j的边尝试转移。

不难发现这样做总复杂度是 O ( m 2 ) O(m^2) O(m2)(每一对"边"最多被拿出两次)

  • 100pts

优化建图,目标是将边数减少到 O ( n ) O(n) O(n)级别。

先给出做法,再尝试证明。

做法:

我们把边分成两种,一种是两端点的颜色相同,另一种是不同。

对于前一种边,会形成若干个连通块。对于每一个连通块,如果是二分图,就做一颗生成树取代原连通块的边;如果不是就再随便找一个点加一个自环。

对于后一种边形成的连通块,由于颜色已经染好了,一定是一个二分图,做一颗生成树替代。

(前一种会形成多个互相独立的连通块,后一个连接各个连通块。)

证明:

我们要让原来的合法路径依然合法,且不会产生新的合法路径。

首先对于一条合法的路径,我们可以从中心划分成若干段,为 0 / 1 0/1 0/1交替段,全是 0 / 1 0/1 0/1段两种。更改原图后,我们只需要让关于中点相对称的两端依然能匹配(设为 A A A, B B B两段)。

原来路径依然合法

1.具体的长度不用考虑,只需要考虑段长的奇偶性。

由于点边可以重复经过,若 A A A段短了,可以重复走点走回来增加长度。但重复走点不会改变奇偶性。

2.对于一个同色连通块,只要两点能连通,且路径奇偶性不变即可

故如果有环,二分图中不同路径奇偶性不变,但是非二分图奇偶性要改变,多加一个自环就可以实现。

3.对于一个不同色连通块同理。

4.同色非二分图连通块中的任意一个点都可加自环,且可加任意多个(至少一个)

由2显然。
注意若加自环的点刚好也在另一个不同色连通块 S S S中,不会对 S S S产生影响。因为走两遍该点就不属于不同色段了。

原来不合法路径依然不合法

由上述证明,该建图方式利用的合法路径奇偶性的特点。
若原不合法路径奇偶性不合法,建图后奇偶性依然不合法。


CODE

#include<bits/stdc++.h>
using namespace std;
#define sf scanf
#define ri register int
#define in red()
#define gc getchar()
#define cs const
#define ll long long
inline int red(){
	int num=0,f=1;char c=gc;
	for(;!isdigit(c);c=gc)if(c=='-')f=-1;
	for(;isdigit(c);c=gc)num=(num<<1)+(num<<3)+(c^48);
	return num*f;
}
cs int N=5e3+10,M=5e5+10;
int n,m,Q;
bool f[N][N],col[N];
vector<int>g[N];
struct edge{
	int u,v;
}e[M];
struct BCJ{
	int fa[N],d[N],ins[N];
	inline int find(int u){
		if(!fa[u])return u;
		int t=fa[u];
		fa[u]=find(fa[u]);
		d[u]^=d[t];
		return fa[u];
	}
	inline void merge(int x,int y,int col){
		int fx=find(x),fy=find(y);
		if(fx^fy){
			d[fx]=d[x]^d[y]^1;
			fa[fx]=fy;
			g[x].push_back(y);
			g[y].push_back(x);
			ins[fy]|=ins[fx];
		}
		else{
			if(d[x]^d[y]^1)ins[fx]=1;
		}
	}
}G[2];
queue<edge> q;
inline void solve(){
	for(ri i=1;i<=n;++i)f[i][i]=1,q.push((edge){i,i});
	for(ri u=1;u<=n;++u){
		for(ri i=g[u].size()-1;i>=0;--i){
			int v=g[u][i];
			if(!f[u][v]&&col[u]==col[v]){
				f[u][v]=f[v][u]=1;
				q.push((edge){u,v});
			}
		}
	}
	while(!q.empty()){
		edge t=q.front();q.pop();
		int x=t.u,y=t.v;		 
		for(ri i=g[x].size()-1;i>=0;--i){
			for(ri j=g[y].size()-1;j>=0;--j){
				int vx=g[x][i],vy=g[y][j];
				if(col[vx]==col[vy]&&!f[vx][vy]){
					f[vx][vy]=f[vy][vx]=1;
					q.push((edge){vx,vy});
				}
			}
		}
	}
}
char s[N];
signed main(){
	n=in;m=in;Q=in;
	sf("%s",s+1);
	for(ri i=1;i<=n;++i){
		col[i]=s[i]^48;
	}
	for(ri i=1;i<=m;++i){
		e[i]=(edge){in,in};
	}
	for(ri i=1;i<=m;++i){
		G[col[e[i].u]^col[e[i].v]].merge(e[i].u,e[i].v,col[e[i].u]^col[e[i].v]);			
	}
	for(ri i=1;i<=n;++i)if(G[0].find(i)==i&&G[0].ins[i])g[i].push_back(i);
	solve();
	while(Q--){
		int x=in,y=in;
		if(f[x][y])cout<<"YES\n";
		else cout<<"NO\n";
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值