济南 CSP-J 刷题营 Day5 图论
Solution
T1 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的树
原题链接
简要思路
-
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} ...\)
每次询问查询这个即可,由于根节点没有父亲,所以可能要特殊处理根节点的答案。
完整代码
- 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的序列
原题链接
简要思路
拓扑排序。
每个输入的相邻的两个点之间建边,然后每次找入度最小的点,用堆来维护,直至遍历完整个序列。
完整代码
//总体思路:拓扑排序(博客
#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的最小生成树
原题链接
简要思路
暂时只会 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;
}