题目整理-1(练习1)

T1Round Dance

题面描述:

\(n\) 个人围成了若干个圆圈,其中每一个圈的人数都大于等于 \(2\),给定这 \(n\) 个人每个人的相邻的人之一,求出满足此条件下这 \(n\) 个人组成圆圈的最大以及最小数量。

思路:

将每个人抽象化为一个点,则该点在组成圆圈的时候,有且仅有不同的两个点与它相连。对于每一个点,题目中给出了与它相连的一个点,我们给这两点建无向边,然后进行研究。

经过我们的推断,我们可以得出最大值就是联通块的数量,因为此时联通块内的点必定在同一环内,要使环的数量最多,我们就不能使任意两个联通块中的点(人)在同一个圆圈内,因此,最大值解决。

通过我们对样例以及观察推断,我们可以发现每个联通块要么是一个,要么是一条。因为一个人最多和两个人相邻,所以无法出现一个点(人)连三个及以上点数,因此,每个联通块只能使链和环。进一步想:

  1. 当联通块为环时,圆圈的形状已经确定,所以它必然是一个单独的圆圈。
  2. 当联通块为链时,两侧的端点处的点仅连了一条边,所以我们可以将所有的环的端点处首尾顺次相连,得到一个大环,此时值最小。

因此,设联通块中环的数量为 \(num_1\) ,链的数量为 \(num_2\),环(圆圈)的数量为 \(k\),则:

\[k_{max}=num_1+num_2\\k_{min}=num_1+[num_1\ne num_2] \]

标签:

DFS
图论

代码实现

this
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int T,n,a[N],b,vis[N],t[N];
int cnt,ans1,ans2;
set<int> g[N];
void dfs(int x,int fa){
	vis[x]=1;
	for(auto i:g[x]){
		if(!vis[i]) dfs(i,x);
		else if(i!=fa) b=1;
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		ans1=ans2=0;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			g[i].insert(a[i]),g[a[i]].insert(i);
		}
		for(int i=1;i<=n;i++)
			if(!vis[i]) dfs(i,-1),ans2++,ans1+=b,b=0;
		for(int i=1;i<=n;i++) vis[i]=0,g[i].clear();
		cout<<ans1+(ans1!=ans2)<<" "<<ans2<<"\n";
	}
	return 0;
}

T2Path Queries

题面描述:

给你一个 \(n\) 个节点的树,并且给 \(m\) 个询问,每个询问包含一个数字 \(q\),求在这棵树中简单路径上边的最大值不超过 \(q\) 的方案数。

思路:

过程类似于最小生成树,但是原理不同。

先定义 \(ans_i\),表示在这棵树上简单路径上边的最大值为 \(i\) 的方案数。

过程:

  1. 对所有边按路径长度排序,再按照最小生成树的方式从按边权小到大加边。
  2. 在加边的同时,我们设这条边的权值为 \(w\),联通的两个联通块内的点数分别为 \(siz_i\)\(siz_j\),则可以得到式子:

\[ans_w+=siz_i\times siz_j \]

意思是在原本未联通的两个联通块中,每个点都可以与另外的一个联通块中的任意一点组成一条简单路径,且最大值为 \(w\)

  1. \(ans\) 数组求前缀和,\(ans_i\) 的意义就变为了这棵树中简单路径上边的最大值不超过 \(i\) 的方案数

标签

图论
并查集
计数
前缀和
排序

代码实现:

this
#include<bits/stdc++.h>
#define N 200005
#define ll long long
using namespace std;
int n,m,fa[N],siz[N];
ll ans[N];
struct node{ int a,b,w; }e[N];
int cmp(node a,node b){ return a.w<b.w; }
int fd_fa(int x){ return ((fa[x]==x)?x:(fa[x]=fd_fa(fa[x]))); }
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	for(int i=1;i<n;i++) cin>>e[i].a>>e[i].b>>e[i].w;
	sort(e+1,e+n+1,cmp);
	for(int i=1;i<=n;i++){
		e[i].a=fd_fa(e[i].a),e[i].b=fd_fa(e[i].b);
		ans[e[i].w]+=(ll)siz[e[i].a]*siz[e[i].b];
		fa[e[i].b]=e[i].a,siz[e[i].a]+=siz[e[i].b];
	}
	for(int i=1;i<=2e5;i++) ans[i]+=ans[i-1];
	while(m--){
		cin>>n;
		cout<<ans[n]<<" ";
	}
	return 0;
}

T3Vlad and the Mountains

题面描述:

给你一个有 \(n\) 个点、\(m\) 条边的无向图,点 \(i\) 的点权为 \(h_i\)。若你的能量值为 \(e\),当你从点 \(i\) 到达点 \(j\) 时,则能量值变为 \(e-(h_j-h_i)\)。给定 \(q\) 个询问,每个询问形如 \(s\ t\ e\) ,表示询问初始能量值为 \(e\) ,能否从点 \(s\) 走到点 \(t\)。注:若能量值变为为负,则你将死去。

思路:

我们先推导一下当初始能量值为 \(e\) 时,从点 \(s\) 到达点 \(t\) 后所剩的能量值。
eg.

此时最终到达 \(t\) 时的能量值为:

\[e-(h_1-h_s)-(h_2-h_1)-(h_3-h_2)-(h_t-h_s)\\e+h_s-h_1+h_1-h_2+h_2-h_3+h_3-h_t\\e+h_s-h_t \]

所以,我们可以得出,对于这种计算最终能量值的方式,最终的能量值只与起点与终点的能量值有关!!!

好了,终于可以步入正轨了。在一条路径中,我们要求到达途中经过的每一个点时能量值都不能小于零,所以,为何不可理解为从起点出发到到达点 \(t\) 的路径上的每一个点最终的能量值都不可小于零呢?

完全可以,所以我们需要判断这个路径上的点 \(i\) 中最大的 \(h_i\),只要这个 \(h_i\)\(e+h_s\) 小,就可以成功到达!

所以,我们直接一个操作,对于每个询问,直接把所有点权大于 \(e+h_s\) 的点一删,顺手判断一下 \(s\)\(t\) 是否联通不就可以了吗?

好吧。。。这复杂度确实有点大了,考虑一下反着操作,既先将所有的操作按照 \(e+h_s\) 的大小从小到大拍个序,再依次加点以及可连的边,最后判断一下 \(s\)\(t\) 是否联通,记录答案即可。

这个题终于完了

标签

图论
排序

代码实现

this
#include<bits/stdc++.h>
#define pr pair<int,int>
#define pr4 pair<pr,pr >
#define fr first
#define se second
#define N 200005
using namespace std;
int T,n,m,q,fa[N],ans[N],vis[N];
int tot,head[N];
pr a[N];
pr4 b[N];
struct edge{
	int to,next;
	void add(int u,int v){ to=v,next=head[u],head[u]=tot; }
}e[N<<1];
int fd_fa(int x){ return (fa[x]==x)?x:(fa[x]=fd_fa(fa[x])); }
int main(){
	cin>>T;
	while(T--){
		tot=0;
		memset(head,0,sizeof(head));
		memset(ans,0,sizeof(ans));
		memset(vis,0,sizeof(vis));
		cin>>n>>m;
		for(int i=1;i<=n;i++) cin>>a[i].fr,a[i].se=fa[i]=i;
		for(int i=1;i<=m;i++){
			int u,v;
			cin>>u>>v;
			e[++tot].add(u,v);
			e[++tot].add(v,u);
		}
		cin>>q;
		for(int i=1;i<=q;i++){
			cin>>b[i].se.fr>>b[i].se.se>>b[i].fr.fr;
			b[i].fr.fr+=a[b[i].se.fr].fr;
			b[i].fr.se=i;
		}
		sort(a+1,a+n+1);
		sort(b+1,b+q+1);
		tot=1;
		for(int i=1;i<=q;i++){
			while(tot<=n&&a[tot].fr<=b[i].fr.fr){
				int faa=fd_fa(a[tot].se),fab;
				vis[a[tot].se]=1;
				for(int j=head[a[tot].se];j;j=e[j].next){
					fab=fd_fa(e[j].to);
					if(fab!=faa&&vis[e[j].to]) fa[fab]=faa;
				}
				tot++;
			}
			ans[b[i].fr.se]=(fd_fa(b[i].se.fr)==fd_fa(b[i].se.se));
		}
		for(int i=1;i<=q;i++) cout<<(ans[i]?"YES\n":"NO\n");
	}
	return 0;
}
---

T4Information Graph

题面描述

在某公司中有 \(n\) 名员工,开始时员工之间没有任何关系,接下来会有 \(m\) 个操作。

  1. \(y\) 成为了 \(x\) 的上司( \(x\) 在那之前不会有上司)

  2. 员工 \(x\) 得到了一份文件,然后 \(x\) 把文件传给了他的上司,然后上司又传给了他的上司,以此类推,直到某人没有上司,将文件销毁

  3. 询问 \(x\) 是否看过某份文件。

思路:

其实可以直接转换为雨天的尾巴这道题,具体过程如下:

  1. 记录操作 \(2\)\(x\) 以及他/她的最终上司 \(y\),并且记录文件为 \(z\).

  2. 离线查询。

  3. 完美转换。

此时题面变为:

给你一个森林,有操作为:从 \(x\)\(y\) 的路径上发放一袋 \(z\) 类型的物品,有查询为:查询点 \(x\) 上是否存在类型 \(z\) 的物品。

这不直接几乎原题了吗。

好了,所以我们使用线段树合并加上树上差分,完美解决此题!

标签

线段树合并
树上差分

代码实现:

this
#include<bits/stdc++.h>
#define pr3 pair<pair<int,int>,int>
#define pr pair<int,int>
#define mpr3(a,b,c) make_pair(make_pair(a,b),c)
#define mpr(a,b) make_pair(a,b)
#define fr first
#define se second
#define N 100005
#define ll long long
using namespace std;
struct x_tree{
	#define M N<<5
	int idx;
	int sm[M],ls[M],rs[M];
	#undef M
	#define mid ((l+r)>>1)
	void push_up(int id){ sm[id]=sm[ls[id]]+sm[rs[id]]; }
	void add(int &id,int l,int r,int k,int s){
		if(!id) id=++idx;
		if(l==r){ sm[id]+=s;return; }
		if(mid>=k) add(ls[id],l,mid,k,s);
		else add(rs[id],mid+1,r,k,s);
		push_up(id);
	}
	int sum(int id,int l,int r,int k){
		if(!sm[id]) return 0;
		if(l==r) return sm[id];
		if(mid>=k) return sum(ls[id],l,mid,k);
		return sum(rs[id],mid+1,r,k);
	}
	void he(int &id,int td,int l,int r){
		if(!td) return;
		if(!id){ id=td; return; }
		sm[id]+=sm[td];
		if(l==r) return;
		he(ls[id],ls[td],l,mid);
		he(rs[id],rs[td],mid+1,r);
	}
}t;
int cnt,num,cnt1,n,m,fa[N],ans[N];
pr3 a[N];
int tot,head[N],vis[N],dad[N];
vector<pr> g[N];
struct edge{
	int to,next;
	void add(int u,int v){ to=v,next=head[u],head[u]=tot; }
}e[N];
int fd_fa(int x){ return (fa[x]==x)?x:(fa[x]=fd_fa(fa[x])); }
void dfs(int x){
	vis[x]=1;
	#define y e[i].to
	for(int i=head[x];i;i=e[i].next){
		dfs(y);
		t.he(x,y,1,1e5);
	}
	for(auto h:g[x]){
		ans[h.se]=t.sum(x,1,1e5,h.fr);
	}
	#undef y
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	t.idx=n;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++){
		int opt,x,y;
		cin>>opt>>x;
		if(opt!=2) cin>>y;
		else ++num,a[++cnt]=mpr3(x,fd_fa(x),num);
		if(opt==1) fa[x]=fd_fa(y),dad[x]=y,e[++tot].add(y,x);
		if(opt==3) cnt1++,g[x].push_back(mpr(y,cnt1));
	}
	for(int i=1;i<=cnt;i++){
		int x=a[i].fr.fr,y=a[i].fr.se,z=a[i].se;
		t.add(x,1,1e5,z,1);
		if(dad[y]) t.add(dad[y],1,1e5,z,-1);
	}
	for(int i=1;i<=n;i++){
		if(!vis[i]) dfs(fd_fa(i));
	}
	for(int i=1;i<=cnt1;i++) cout<<(ans[i]?"YES\n":"NO\n");
	return 0;
}

T5白雪皑皑

题面描述:

给出一个长度为 \(n\) 的序列,有 \(m\) 次操作,第 \(i\) 次操作表示将 \((i\times p+q) \ mod \ n\)\((i\times q+p) \ mod \ n\) 的序列中所有的值改为 \(n\)。输出所有操作后的序列。

思路:

。。。
第一眼:线段树板子题?!
第一次提交:怎么 \(T\) 一个点???
\(n\) 眼,哦~~,只需要最后的 \(n\) 次操作就行了!!!
\(n\) 次提交:过了。。。
易证

标签

线段树
数论

代码实现:

this
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define N 1000005
using namespace std;
template<class T,ll count>
struct x_tree{
	x_tree<T,count-1> s[2];
	#define mid (1<<(count-1))
	#define len (1<<count)
	T sm,lz;
	il x_tree(){ sm=lz=0; }
	il void x_add(T k){ sm=k*len,lz=k; }
	il void push_up(){ sm=s[0].sm+s[1].sm; }
	il void push_down(){
		if(!lz) return;
		s[0].x_add(lz);
		s[1].x_add(lz);
		lz=0;
	}
	il void add(int l,int r,T k){
		if(l<=1&&len<=r) x_add(k);
		else{
			push_down();
			if(mid>=l) s[0].add(l,r,k);
			if(mid<r) s[1].add(l-mid,r-mid,k);
			push_up();
		}
	}
	il T sum(int k){
		push_down();
		if(mid>=k) return s[0].sum(k);
		return s[1].sum(k-mid);
	}
	#undef len
	#undef mid
};
template<class T>
struct x_tree<T,0>{
	T sm;
	il x_tree(){ sm=0; }
	il void x_add(T k){ sm=k; }
	il void add(int l,int r,T k){ sm=k; }
	il T sum(int k){ return sm; }
};
x_tree<int,20> t;
int n,m,p,q;
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m>>p>>q;
	for(int i=max(1,m-n+1);i<=m;i++){
		int l=((ll)i*p%n+q)%n+1;
		int r=((ll)i*q%n+p)%n+1;
		if(l>r) swap(l,r);
		t.add(l,r,i);
	}
	for(int i=1;i<=n;i++) cout<<t.sum(i)<<"\n";
	return 0;
}

template线段树在没有动态开点时

万岁!!!


注:

  1. 题号为本校OJ上的链接,题名为原出处链接。

$$The\ end$$

posted @ 2025-01-23 21:50  skx_515  阅读(25)  评论(0)    收藏  举报