济南 CSP-J 刷题营 Day5 图论

Solution

T1 emoairx的二叉树

原题链接

4114: emoairx的二叉树

简要思路

一道简单的递归签到题,每次找到较大的数进行除以 \(2\),每次递归把步数加一,直到两个点走到同一个点上。

完整代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int m;
int ans(int x,int y,int num){
	if(x==y){//一样结束递归
		return num;
	}
	if(x>y){
		return ans(x/2,y,++num);//分讨
	}
	if(x<y){
		return ans(x,y/2,++num);//步数勿忘 +1
	}
}
signed main(){
	cin>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		int num=0;//步数初始值
		cout<<ans(x,y,num)<<endl;//直接输出
	}
	return 0;
}

T2 emoairx的树

原题链接

4115: emoairx的树

简要思路

  • 50 pts
    暴搜。

  • 70 pts
    \(k=1\) 时,直接输出他的度数即可。

  • 80 pts
    \(k=2\) 时,其实暴搜也可以过,但是要注意加上剪枝。

  • 100 pts
    对于全部数据,我们可以参考 \(k=2\) 的做法,考虑一个简单的 容斥
    定义一个数组 \(f\)\(f_{i,j}\) 代表所有 \(i\) 的子树中,距离点 \(i\)\(j\) 的长度的点的数量。提前预处理出 \(f\) 数组的值。
    那么如果求 \(x\) 点的 \(k\) 阶度数,公式:
    \(f_{a_1,k}+f_{a_2,k-1}+f_{a_1,k-2}-f_{a_2,k-3} ...\)
    每次询问查询这个即可,由于根节点没有父亲,所以可能要特殊处理根节点的答案。

image

完整代码

  • 80 pts
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=100005;

vector<int> z[MAXN];
int degrees[MAXN];
int ans[MAXN][55];
bool vis[MAXN];
int num;

void dfs(int s,int degree,int k){//无需多言,暴搜 DFS
	if(degree==k){
		num++;
		return;
	}
	
	for(int i=0;i<z[s].size();i++){
		if(!vis[z[s][i]]){
			vis[z[s][i]]=1;
			dfs(z[s][i],degree+1,k);
			vis[z[s][i]]=0;
		}
	}
}

void add_edge(int s,int e){//加边
	z[s].push_back(e);
	z[e].push_back(s);
}

signed main() {
	int n,m;
	cin>>n>>m;
	
	for(int i=0;i<n-1;i++) {
		int s,e; 
		cin>>s>>e;
		add_edge(s,e);
	}
	
	while(m--){
		int x,k;
		cin>>x>>k;
		if(k==0)cout<<1<<endl;
		else if(k==1)cout<<z[x].size()<<endl;//两组特判
		else{
			num=0;
			vis[x]=1;
			dfs(x,0,k);
			vis[x]=0;
			cout<<num<<endl;
		}
	}
	
	return 0;
}
  • 100 pts
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;

int n,a[N],m,y,x,dis[N][55],fa[N];
int Next[N*2],head[N],to[N*2],nedge;

void add(int a,int b){//链式前向星,可以用 vector
	Next[++nedge]=head[a];head[a]=nedge;to[nedge]=b;
}
void add_ne(int x,int y){
	add(x,y);add(y,x);//建边
}

void dfs(int x){//搜索
	dis[x][0]=1;
	for (int i=head[x];i;i=Next[i]){
		int V=to[i];
		if (V==fa[x]) continue;
		fa[V]=x;
		dfs(V);
		for(int j=1;j<=50;j++)
			dis[x][j]+=dis[V][j-1]; 
	}
}

int get_ans(int x,int k){
	int ans=dis[x][k];
	k--;
	int i=x;
	for (;i!=1&&k;i=fa[i],k--){
		ans=ans+dis[fa[i]][k]-dis[i][k-1];//减的保证不是回溯的答案
	}
	if (i!=1&&k==0) ans++;//一直找父亲,并且保证不是根节点
	return ans;
}

signed main(){
	cin>>n>>m;
	for(int i=1;i<n;i++){
		int s,e;
		cin>>s>>e;
		add_ne(s,e);
	}
	dfs(1);
	for(int i=1;i<=m;i++){
		int x,k;
		cin>>x>>k;
		cout<<get_ans(x,k)<<endl; 
	}
	return 0;
}

T3 emoairx的序列

原题链接

4116: emoairx的序列

简要思路

拓扑排序。
每个输入的相邻的两个点之间建边,然后每次找入度最小的点,用堆来维护,直至遍历完整个序列。

完整代码

//总体思路:拓扑排序(博客
#include<cstdio>
#include<queue>
using namespace std;
int n,mo,ans,cnt,s;
priority_queue<int>q;//定义大根堆
int d[1000005],head[1000005];//d 为入度,head 为链式前向星内部
bool in[1000005];//in 相当于 vis,判断是否出现
inline int read(){
	int ret=0;char c=getchar();
	while((c>'9')||(c<'0'))c=getchar();
	while((c>='0')&&(c<='9'))ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ret;
}
struct edge{
	int to,next;
}e[1000005];
void add(int u,int v){e[++cnt]=(edge){v,head[u]};head[u]=cnt;d[v]++;}//链式前向星,可用 vector
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		int x=read(),last=read();in[last]=true;
		for(int j=1;j<x;j++){//和上一个相邻的点建边
			int now=read();in[now]=true;
			add(last,now);
			last=now;//每一个相邻的点都要建边
		}
	}
	for(int i=1;i<=1000000;i++)if((in[i])&&(d[i]==0))q.push(n-i);//相当于取反,等同于小跟堆
	
	while(!q.empty()){
		int u=n-q.top();//同 29
		q.pop();
		for(int i=head[u];i>0;i=e[i].next){
			d[e[i].to]--;
			if(d[e[i].to]==0)q.push(n-e[i].to);//同 32
		}
		printf("%d ",u);
	}
	
	return 0;
}

T4 emoairx的最小生成树

原题链接

4117: emoairx的最小生成树

简要思路

暂时只会 30pts 的写法,最小生成树的板子。

非完整代码

//O(nm) 
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3e5+5;

int to[MAXN];//to[i] 表示 i 点在并查集里面的箭头指向谁

int go(int p){//看一下点 p 沿着并查集箭头最后会走到哪里 
	if(to[p]==p)return p;//指向自己
	else return go(to[p]);//递归调用 
} 

struct edge{
	int s,e,d;
}ed[MAXN];//ed[i] 代表第 i 条边是在 s 与 e 之间的长度为 d 的边

int n;

bool cmp(edge a,edge b){
	return a.d<b.d;
}

int a[MAXN];

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	
	int num=0;
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++){
			num++;
			ed[num].s=i;
			ed[num].e=j;
			ed[num].d=max(a[i],a[j])%min(a[i],a[j]);
		}
	
	sort(ed+1,ed+num+1,cmp);//所有边按照边权进行排序
	
	for(int i=1;i<=n;i++)
		to[i]=i;//初始化并查集
	
	int ans=0;//生成树大小
	int cnt=0;//边的数量 
	for(int i=1;i<=num;i++){
		if(cnt==n-1)break; 
		int p1=ed[i].s,p2=ed[i].e,d=ed[i].d;
		if(go(p1)!=go(p2)){//不在同一个连通块 
			ans+=d;
			to[go(p1)]=go(p2);
			++cnt; 
		}
	}
	
	cout<<ans<<endl;		
} 

讲课笔记

基本定义

https://www.cnblogs.com/weeping/p/6847112.html

posted @ 2023-07-28 21:19  CheZiHe929  阅读(26)  评论(0编辑  收藏  举报