2021年巴西icpc重现赛补题

Party Company

大意:

有一颗树,n个节点,每一个节点有一个对应的权值,并且满足儿子的权值<父亲的权值。现有m个区间【L,R】,每一个区间会给定一个初始点(举办方),其它点想要加入该区间,需同时满足以下条件:

1.其权值在该区间内(左右包含)

2.其父亲/儿子已经加入了该区间

现在给定树和若干区间,求每一个节点能被多少个区间覆盖。

n,m<=1e5

思路:

感觉还算是比较综合的一道题了。

稍微转化以下题意,不难发现,对于每一个区间以及其初始点,我们其实就是要找一个包含该初始点的连通块,其中连通块内的每一个点的权值都在该区间内,然后里面的每一个点的答案就可以+1了。

不难证明该思路的合理性。设该思路每次对应的点数是A,实际答案是B

先证明A<=B:

首先连通块内的点的权值都是满足条件的,并且它们与初始点的路径上的每一个点都被加入了集合,所以A<=B的。 

证明A>=B:

不在连通块内的点,就算它的权值在区间内部,但是其与初始点的路径上必然存在某一个点没有被加入集合,所以该点一定不在答案集合中,故A<=B

这就得到了该思路的合理性。

那么考虑到每一个点的父亲只有一个,赛时也很快就想到了先去找到深度最浅的满足条件的点。为什么这么做?因为就算是连通块,也依然是有些混乱,肯定不如我们最会处理的子树问题和区间问题来的清晰,所以就要考虑将其转化。

所以首先用树上倍增找到初始点到根的路径上的满足权值要求的深度最浅的点RT,那么不难发现能被加入该集合的所有点一定都在RT的子树内,否则,其与RT的lca就会是一个深度更浅的点,但RT已经是能找到的深度最浅的点了,所以矛盾。

void init_dfs(ll id,ll p)
{
	fa[id][0]=p;
	for(int i=1;i<=20;++i) fa[id][i]=fa[fa[id][i-1]][i-1];
	for(int i=head[id];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==p) continue;
		init_dfs(y,id);
	}
}
ll jump(ll id,ll tar)
{
	ll now=id;
	for(int i=20;i>=0;--i)
	{
		if(age[fa[now][i]]<=tar&&fa[now][i]!=0) now=fa[now][i];
	} 
	return now;
}

到这里还是很好处理的,并且我们也已经成功把问题转化到RT的子树上了。并且,由于子树内的点的权值都小于RT的权值,相当于它们都满足权值<=R的条件,所以只要权值>=L就一定满足条件。你可能会想到,就算权值>=L,但有可能其某个父亲没有加入集合,这样就是不合理的,但是我们同样考虑到权值的单调性,祖先权值一定>儿子权值,所以这种情况不存在。因此,对于子树中的点,我们现在只用考虑其权值>=L即可。但是赛时就是卡在了这里,不知道该怎么做下去了。(还是太年轻~)

看到别人代码时还是有点傻眼了,没想到这么简单(其实也不简单,但人家真的写的很简单,哭...)。我们考虑离线,这个不难想,在线处理那么多询问肯定不太行。对于每一次的区间,我们先找到它对应的RT,然后把这个区间的信息存在RT里。最后做一次dfs,对权值开一个树状数组(1e5),对于每一个节点,将存放在该点的区间信息的L值在树状数组中+1,那么改点的答案就是一个树状数组的前缀和了,这个显然。然后该点一定是对应区间的RT,所以向下dfs时,其子树的节点也能利用这些区间。但是别的子树的节点不行,所以子树dfs完之后,要把加过的L值-1,去除影响,这个也就是一句话的事,然后就好了。

#include
using namespace std;
#define ll int
#define endl '\n'
#define low(x) x&(-x)
const ll N=1e5+10;
struct ty
{
	ll t,l,next;
}edge[N<<1];
ll cn=0;
ll head[N];
ll n,m;
ll age[N];
ll fa[N][25];
ll a,b,c;
struct que
{
	ll l,r;
};
vector vt[N];
ll ans[N];
void init_dfs(ll id,ll p)
{
	fa[id][0]=p;
	for(int i=1;i<=20;++i) fa[id][i]=fa[fa[id][i-1]][i-1];
	for(int i=head[id];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==p) continue;
		init_dfs(y,id);
	}
}
ll jump(ll id,ll tar)
{
	ll now=id;
	for(int i=20;i>=0;--i)
	{
		if(age[fa[now][i]]<=tar&&fa[now][i]!=0) now=fa[now][i];
	} 
	return now;
}
void add_edge(ll a,ll b,ll c)
{
	edge[++cn].t=b;
	edge[cn].l=c;
	edge[cn].next=head[a];
	head[a]=cn;
}
ll tr[N<<1];
void add(ll x,ll y)
{
	while(x<=N)
	{
		tr[x]+=y;
		x+=low(x);
	}
}
ll sum(ll x)
{
	ll ans=0;
	while(x)
	{
		ans+=tr[x];
		x-=low(x);
	}
	return ans;
}
void dfs(ll id,ll p)
{
	for(auto i:vt[id])
	{
		add(i.l,1);
	}
	ans[id]=sum(age[id]);
	for(int i=head[id];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==p) continue;
		dfs(y,id);
	}
	for(auto i:vt[id])
	{
		add(i.l,-1);
	}
}
void solve()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>a>>b;
		age[i]=a;
		if(b!=i)
		{
			add_edge(i,b,1);
			add_edge(b,i,1);
		}
	}
//	for(int i=1;i<=n;++i) cout<>a>>b>>c;
		ll dsp=jump(a,c);
		//cout<<"alex "<

Between Us 

大意:

给定一张无向图,现在要将这张图分成两个部分,两张子图连接处的边会去除,要求满足分好之后每一个节点的度数都是奇数。问是否存在这样的分法

1 ≤ n ≤ 100 , 1 ≤ m ≤ n × ( n − 1 ) / 2 

思路:

思路有点小清新,而且实现也是非常的简单,是看了会让人心情舒畅的题。

虽然但是,我们队赛时完全没看过这道题(看了也不会)。

令a【i】表示i点被分配到了哪一个集合。1表示1集合,0表示0集合(废话)

那么就有如下式子:

i点度数是奇数,a【i】=1:\bigoplus_{j->i}a[j]=1,也就是分出偶数个点去0集合

i点度数是奇数,a【i】=0:\bigoplus_{j->i}a[j]=0,也就是分出偶数个点去1集合

i点度数是偶数,a【i】=1:\bigoplus_{j->i}a[j]=1,也就是分出奇数个点去0集合

i点度数是偶数,a【i】=0:\bigoplus_{j->i}a[j]=1,也就是分出奇数个点去1集合

那么就不难发现,若i点度数是奇数,a[i]\wedge (\bigoplus_{j->i}a[j])=0,若i点度数为偶数,\bigoplus_{j->i}a[j]=1.

然后建立对应的方程组,高斯消元就好了。就没了。。。

#include
using namespace std;
#define endl '\n'
#define ll int
const ll N=210;
ll n,m;
vector vt[N];
ll du[N];
bitset S[N];
ll a,b;
ll all,len; 
int guass()
{
	int now=0,last=0;
	int cnt=0;
	for(int i=1;i<=n&&now<=len;++i)
	{
		int j=now;
		while(j<=len&&!S[j][i]) j++;
		if(j>len) continue;
		else cnt++;
		if(j!=now) swap(S[j],S[now]);
		for(int k=1;k<=len;++k)
		{
			if(k==now) continue;
			if(S[k][i]==0) continue;
			S[k]^=S[now];
		}
		now++; 
	}
//	for(int i=0;i>n>>m;
	all=n+1;len=n;
	for(int i=1;i<=m;++i)
	{
		cin>>a>>b;
		vt[a].push_back(b);
		vt[b].push_back(a);
		du[a]++;du[b]++;
	}
	for(int i=1;i<=n;++i)
	{
		if(du[i]&1) //奇数
		{
			S[i][i]=1;
			for(ll j:vt[i])
			{
				S[i][j]=1;
			}
			S[i][all]=0;
	    } 
	    else
	    {
	    	for(ll j:vt[i])
	    	{
	    		S[i][j]=1;
			}
			S[i][all]=1;
		}
	}
	if(guass()) cout<<"Y";
	else cout<<"N";
}
int main()
{
	solve();
	return 0;
} 

 

你可能感兴趣的:(图论,思维题,算法,数据结构)