JLU数据结构(荣誉课)第四次作业
7-1 连通分量 (100 分)
无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。
输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.
输出格式:
1行,1个整数,表示所求连通分量的数目。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
2
思路
使用并查集,初始化每个点的“父亲”为自身(各点自成连通分量),对每一条无向边,考察该边两顶点当前是否在同一连通分量(集合)中(代表元是否相同),若不是,则将两连通分量合并,并将联通分量数-1
代码
#include<iostream>
#include<cstdio>
using namespace std;
int fa[50050];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);//find(fa[x])
}
void add(int x,int y)
{
fa[find(x)]=find(y);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
fa[i]=i;
int x,y;
int cnt=n;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
if(find(x)!=find(y))
{
cnt--;
add(x,y);
}
}
printf("%d",cnt);
return 0;
}
7-2 整数拆分 (100 分)
整数拆分是一个古老又有趣的问题。请给出将正整数 n 拆分成 k 个正整数的所有不重复方案。例如,将 5 拆分成 2 个正整数的不重复方案,有如下2组:(1,4)和(2,3)。注意(1,4) 和(4,1)被视为同一方案。每种方案按递增序输出,所有方案按方案递增序输出。
输入格式:
1行,2个整数n和k,用空格分隔, 1≤k≤n≤50.
输出格式:
若干行,每行一个拆分方案,方案中的数用空格分隔。
最后一行,给出不同拆分方案的总数。
输入样例:
在这里给出一组输入。例如:
5 2
输出样例:
在这里给出相应的输出。例如:
1 4
2 3
2
思路
典型的回溯法递归生成解。此题数据量小,方案可拆分成子方案,子方案与原方案同构。
回溯法可看成对答案树(隐含图)的dfs遍历,根为空解,叶为最终解,中间结点为局部解。
确立dfs状态为(x,num,low)//代表待分解数为x,目前解内元素数量为num,枚举分解数的最小值(枚举起点)为low
终止条件是num达到要求,此时一并检测x是否为0(合法性),为0合法则输出解并计数器++
调用dfs(n,0,1)得到答案
注意:
1. 按题目要求,传入low保证解内元素是不增序列(不会出现(3,2)此类答案)
2.这里按递增顺序逐步生成解(样例两个解的先后顺序)
3.*这里生成的解不用考虑第一次0的特判(好像和不限分解序列长的正整数分解不一样)
代码
#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int t[500];
int cnt=0;
void dfs(int x,int num,int low)
{
if(num==m)//终止
{
if(x==0)//合法性
{
for(int i=0;i<m;i++)
{
if(i)printf(" ");
printf("%d",t[i]);
}
printf("\n");
cnt++;
}
return;
}
for(int i=low;i<=x;i++)//枚举解对应该层次下的内容
{
//if(i==n)break;
if(1)
{
t[num]=i;
dfs(x-i,num+1,i);
}
}
}
int main()
{
scanf("%d %d",&n,&m);
if(m==1)
{
printf("%d\n1",n);
return 0;
}
dfs(n,0,1);
printf("%d",cnt);
return 0;
}
7-3 数字变换 (100 分)
利用变换规则,一个数可以变换成另一个数。变换规则如下:(1)x 变为x+1;(2)x 变为2x;(3)x 变为 x-1。给定两个数x 和 y,至少经过几步变换能让 x 变换成 y.
输入格式:
1行,2个整数x和y,用空格分隔, 1≤x,y≤100000.
输出格式:
第1行,1个整数s,表示变换的最小步数。
第2行,s个数,用空格分隔,表示最少变换时每步变换的结果。规则使用优先级顺序: (1),(2),(3)。
输入样例:
在这里给出一组输入。例如:
2 14
输出样例:
在这里给出相应的输出。例如:
4
3 6 7 14
思路
首先想到用搜索当然如果有数学好的大佬搞出来数论做法的话请当我没说)
最小步数想到bfs
起点是原始数,终点是目标数,中间节点靠上述法则隐式生成(隐式图上跑bfs)
那就分享下我的处理细节:
为了防止可能出现的过度扩展结点
1.入队标记,保证从原始数到目标数间的中间结点最多入队一次
2.降到0(这里是x和y两种大小关系的情况合在一起,所以这么限制,也可以拆出x>y和x<y讨论)去掉
升到比目标数大时只能做-1操作(贪心/剪枝)
这里用path方法,存到当前节点最优解“路径”的前一个结点
注意题目的优先级顺序要求,因此按此要求扩展结点,能保证某个中间节点被第一次搜到时一定是最优的,可以同时更新path
沿着path走回去,入栈统计能得到最短路径及其长度,输出即可
//实际上感觉搜的时候有最优子结构性质在支撑,保证局部最优解逐步生成整体最优
代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<stack>
using namespace std;
queue<int>q;
bool rd[200500];//标记
int bj[200500];//当path数组用(没能望文释义,抱歉)
int cnt=0;
stack<int>s;
int main()
{
int x,y;
//memset(bj,-1,sizeof(bj));
scanf("%d %d",&x,&y);
q.push(x);
rd[x]=1;//初始入队打标记
int top;
while(!q.empty())
{
top=q.front();
q.pop();
if(top<=0)
{
continue;
}
if(top==y)break;//得到解退出
if(top<y)//比目标数小
{//注意优先级顺序
if(!rd[top+1])
{
q.push(top+1);
rd[top+1]=1;
bj[top+1]=top;
}
if(!rd[top*2])
{
q.push(top*2);
rd[top*2]=1;
bj[top*2]=top;
}
}//-1操作这里不分大小关系都能做
if(!rd[top-1])
{
q.push(top-1);
rd[top-1]=1;
bj[top-1]=top;
}
}
int flag=0;
top=y;
while(top!=x)//从终点走回去
{
s.push(top);//入栈,反向输出
top=bj[top];
cnt++;
}
printf("%d\n",cnt);//路径长度
while(!s.empty())
{
if(flag)printf(" ");//路径
flag=1;
printf("%d",s.top());
s.pop();
}
return 0;
}
/*
1.allocator?
2.标记数组当队列用
3.path可行的原因是:
*/
7-4 旅行 I (100 分)
五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。将所有城市编号为1到n,你出发的城市编号是s。你想知道,到其它城市的最小费用分别是多少。如果可能,你想途中多旅行一些城市,在最小费用情况下,到各个城市的途中最多能经过多少城市。
输入格式:
第1行,3个整数n、m、s,用空格分隔,分别表示城市数、交通方式总数、出发城市编号, 1≤s≤n≤10000, 1≤m≤100000 。
第2到m+1行,每行三个整数u、v和w,用空格分隔,表示城市u和城市v的一种双向交通方式费用为w , 1≤w≤10000。
输出格式:
第1行,若干个整数Pi,用空格分隔,Pi表示s能到达的城市i的最小费用,1≤i≤n,按城市号递增顺序。
第2行,若干个整数Ci,Ci表示在最小费用情况下,s到城市i的最多经过的城市数,1≤i≤n,按城市号递增顺序。
输入样例:
在这里给出一组输入。例如:
5 5 1
1 2 2
1 4 5
2 3 4
3 5 7
4 5 8
输出样例:
在这里给出相应的输出。例如:
0 2 6 5 13
0 1 2 1 3
思路
先跑最短路,在拿最短路用bfs按经过城市最多搜解
最短路是优先队列堆优化Dijkstra(大家还是去网上找模板看吧,原理讲的更详细)
//优先队列不支持修改优先级,重复松弛会造成重复入队再出队判重,时间复杂度eloge<elogn
这里附上的优先队列堆优化Dijkstra代码属于封装到结构式的模板,变量名有点怪(所以要起些望文生义的名字不然很坑人)
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=100050;
const int inf=2000000000;
#define fo(i,a,b) for(int i=(a);i<=(b);i++)
struct ed
{
int fr,po,dis;
ed(int u,int v,int s):fr(u),po(v),dis(s){
}
};
struct hn
{
int di,pos;
bool operator<(const hn& zs)const{
return di>zs.di;
}
};
struct dik
{
int n,m;
vector<ed> eds;
vector<int> g[maxn];
bool don[maxn];
int d[maxn];
int p[maxn];
queue<hn>qq;
int bs[maxn];
void iiis(int n)
{
this->n=n;
fo(i,0,n-1)g[i].clear();
eds.clear();
}
void added(int fr,int po,int dis)
{
eds.push_back(ed(fr,po,dis));
eds.push_back(ed(po,fr,dis));
m=eds.size();
g[fr].push_back(m-2);
g[po].push_back(m-1);
}
void dijk(int ss)
{
priority_queue<hn>q;
fo(i,0,n-1)d[i]=inf;
d[ss]=0;
memset(don,0,sizeof(don));
q.push((hn){0,ss});
while(!q.empty())
{
hn tmp=q.top();q.pop();
int u=tmp.pos;
if(don[u])continue;
don[u]=1;
for(int i=0;i<g[u].size();i++)
{
ed&e=eds[g[u][i]];
if(d[e.po]>d[u]+e.dis)
{
d[e.po]=d[u]+e.dis;
p[e.po]=g[u][i];
q.push((hn){d[e.po],e.po
});
//if(e.po==4)cout<<u<<" "<<e.dis<<endl;
}
}
}
}//上面是模板
void bfs(int ss)
{
qq.push((hn){0,ss});//已走过距离,当前点
qq.push((hn){-1,-1});
hn tmp;
int cnt=0;
int u;
int flag=0;
while(!qq.empty())
{
tmp=qq.front();
qq.pop();
u=tmp.pos;
if(u==-1)
{
cnt++;
qq.push((hn){-1,-1});
if(flag)break;
flag=1;
continue;//计层次的一种方法,注意防无限循环
}
flag=0;
if(tmp.di==d[u])
{
if(cnt>bs[u])
{
bs[u]=cnt;
}
}
for(int i=0;i<g[u].size();i++)
{
ed&e=eds[g[u][i]];
if(e.dis+tmp.di<=d[e.po])
{
qq.push((hn){e.dis+tmp.di,e.po});
}
}
}
}
}dikk;
using namespace std;
int main()
{
int n,t,m,u,v,s;
cin>>n>>m>>t;
dikk.m=m;
dikk.iiis(n+1);
fo(i,1,m)
{
cin>>u>>v>>s;
dikk.added(u,v,s);
}
dikk.dijk(t);
dikk.bfs(t);
cout<<dikk.d[1];
fo(i,2,n)
cout<<" "<<dikk.d[i];
cout<<endl;
cout<<dikk.bs[1];
fo(i,2,n)
cout<<" "<<dikk.bs[i];
return 0;
}