JLU数据结构第四次上机实验

7-1 连通分量


代码长度限制 16 KB
时间限制 200 ms
内存限制 10 MB


题目描述

无向图 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

思路

用树维护一个并查集,最初每个数据视为一个集合,即一个连通分量,每次合并成功就代表减少一个连通分量

代码

#include<iostream>
using namespace  std;
int findf(int *f,int nd,int&nf){
    while(f[nd]!=nd){
        nd=f[nd];
        nf++;
    }
    return nd;
}
int main(){
    int n=0,m=0;
    scanf("%d %d",&n,&m);

    int*f=new int[n+1];
    for(int i=1;i<n+1;i++)f[i]=i;

    int m1,m2,f1,f2;
    int N=n;
    for(int i=0;i<m;i++){
        scanf(" %d %d",&m1,&m2);
        int nf1=0,nf2=0;
        f1=findf(f,m1,nf1);
        f2=findf(f,m2,nf2);
        if(f1!=f2){
            if(nf1>nf2)f[f2]=f1;
            else f[f1]=f2;
            N--;
        }
    }
    printf("%d",N);
    return 0;
}

7-2 整数拆分


代码长度限制 16 KB
时间限制 100 ms
内存限制 1 MB


题目描述

整数拆分是一个古老又有趣的问题。请给出将正整数 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

思路

回溯法,递归实现。将拆分出的数据用vector a保存,每层递归处理a的一个数据。因为数据升序排列,所以可以计算出下一个待处理位置的最大值(剩余数据/剩余的位置数)M,最小值是前一个数据m,在(m,M)中枚举每种情况。

代码

#include<iostream>
#include<vector>
using namespace std;
vector<int>a;
int N=0;
void dis(int n,int k){
    if(k==1){
        N++;
        a.push_back(n);
        vector<int>::iterator p=a.begin();
        for(int j=0;j<a.size();j++){
            if(j!=0)printf(" ");
            printf("%d",*p);
            p++;
        }
        printf("\n");
        a.pop_back();
        return;
    }
    int i=1;
    if(!a.empty())i=a.back();
    while(i*(k-1)<=n-i){
        a.push_back(i);
        dis(n-i,k-1);
        a.pop_back();
        i++;
    }
    return;
}
int main(){
    int n,k;
    scanf("%d %d",&n,&k);
    dis(n,k);
    printf("%d",N);
    return 0;
}

7-3 数字变换


代码长度限制 16 KB
时间限制 100 ms
内存限制 5 MB


题目描述

利用变换规则,一个数可以变换成另一个数。变换规则如下:(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

思路

广度优先遍历,x为起点,x+1,2x,x-1为x的三个儿子。用两个辅助数组,分别记录各结点的上一个结点(last)和该节点是否已被访问过。每次从队列取出一个元素,若他的儿子不超界且未被访问(因为先入队的路径一定更短),就入队。重复以上过程,直到遇见目标数(在元素入队前判断),就停止。通过last数组找到最短路径。
超界的情况:入队的元素可能会大于目标元素,这样只能靠x-1访问到目标元素,如果不加处理会浪费大量空间时间。我设定的界限为max=y+y-x+1;这个最大值足够小且能保证不合适的元素会被排除队列外。

代码

#include<iostream>
#include<vector>
#include<stack>
#include<queue>
#include<string.h>
using namespace std;
int max;
inline int next(int n,int i){
    if(i==0)return n+1;
    else if(i==1)return n*2;
    else return n-1;
}
int*ifask;
int*last;
void bfs(int x,int y){
    if(x==y)return;
    ifask[x]=1;
    queue<int>q;
    q.push(x);
    int now;
    while(1){
        now=q.front();
        q.pop();
        for(int i=0;i<3;i++){
            int nxt=next(now,i);
            if(nxt>0&&nxt<::max&&!ifask[nxt]){
                ifask[nxt]=1;
                last[nxt]=now;
                if(nxt==y)return;
                q.push(nxt);
            }
        }
    }
}
int main(){
    int x,y;
    scanf("%d %d",&x,&y);
    if(x>=y){
        printf("%d\n",x-y);
        if(x>y)printf("%d",x-1);
        for(int i=x-2;i>=y;i--){
            printf(" %d",i);
        }
        return 0;
    }
    ::max=y+y-x+1;
    ifask=new int[::max];
    memset(ifask,0,::max*sizeof(int));
    last=new int[::max];
    bfs(x,y);//return 0;

    stack<int>otpt;
    int nstep=0;
    while(y!=x){
        otpt.push(y);
        nstep++;
        y=last[y];
    }
    if(!otpt.empty()){
        printf("%d\n%d",nstep,otpt.top());
        otpt.pop();
        while(!otpt.empty()){
            printf(" %d",otpt.top());
            otpt.pop();
        }
    }else printf("%d\n%d",0,x);
    return 0;
}

7-4 旅行 I


代码长度限制 16 KB
时间限制 1000 ms
内存限制 10 MB


题目描述

五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。将所有城市编号为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

思路

经典的单源最短路问题,这里总结一下常见的几种方法

dijkstra

正权最短路问题最快算法(堆或者说优先队列优化)
从起始点开始,采用贪心算法的策略,每次遍历(松弛) 到始点距离最近且未访问过的顶点的 邻接节点(访问),直到扩展到终点为止

代码

我用的不是这种方法,下面展示的是别人的代码

struct pos
{
	int dis, name;
	pos(int a, int b) :dis(a), name(b) {}
	bool operator<(const pos& b) const//运算符重载使优先队列递增
	{
		return dis > b.dis;
	}
};
void dijkstra()
{
	priority_queue<pos>q;
	q.push(pos(0, s));
	dis[s] = 0;
	while (!q.empty())
	{
		pos cur = q.top();
		q.pop();
		int u = cur.name;
		if (vis[u]) continue;
		vis[u] = 1;
		for (int v = head[u]; ~v; v = e[v].next)
		{
			int t = e[v].to;
			if (dis[t] > e[v].w + dis[u])
			{
				dis[t] = e[v].w + dis[u];
				num[t] = num[u] + 1;
				q.push(pos(dis[t], t));
			}
			else if (dis[t] == e[v].w + dis[u])
			{
				if (num[u] + 1 > num[t])
				{
					num[t] = num[u] + 1;
				}
			}
		}
	}
}

spfa

设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾

关键代码
void dijs(int s){
    queue<int>q;
    dist[s]=0;
    q.push(s);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        vist[now]=0;//是否在队列中,初值为0
        for(int s0=map[now];s0!=-1;s0=as[s0].next){
            int to=as[s0].to;
            if(dist[now]+as[s0].len<dist[to]){
                dist[to]=dist[now]+as[s0].len;
                nvia[to]=nvia[now]+1;
                if(!vist[to]){
                    vist[to]=1;
                    q.push(to);
                }
            }
        }
    }
    return;
}

完整代码

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace  std;
#define Mrl 0x7fffffff
struct side{
    int to,len,next;
};
vector<side>as;
int*map;
void addSide(int from,int to,int len){
    side s0;
    s0.len=len;
    s0.to=to;
    s0.next=map[from];
    as.push_back(s0);
    map[from]=as.size()-1;
    return;
}
int*dist;
int*vist;
int*nvia;
void dijs(int s){
    queue<int>q;
    dist[s]=0;
    q.push(s);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        vist[now]=0;
        for(int s0=map[now];s0!=-1;s0=as[s0].next){
            int to=as[s0].to;
            if(dist[now]+as[s0].len<dist[to]){
                dist[to]=dist[now]+as[s0].len;
                nvia[to]=nvia[now]+1;
                if(!vist[to]){
                    vist[to]=1;
                    q.push(to);
                }
            }else
            if(dist[now]+as[s0].len==dist[to]){
                if(nvia[now]+1>nvia[to]){
                    nvia[to]=nvia[now]+1;
                    q.push(to);
                }
            }
        }
    }
    return;
}
int main(){
    int n,m,s;
    scanf("%d %d %d",&n,&m,&s);
    {map=new int[n];
    dist=new int[n];
    vist=new int[n];
    nvia=new int[n];}
    for(int i=0;i<n;i++){
        map[i]=-1;
        dist[i]=Mrl;
        vist[i]=0;
        nvia[i]=0;
    }
    for(int i=0;i<m;i++){
        int n1,n2,l;
        scanf(" %d %d %d",&n1,&n2,&l);
        n1--;n2--;
        addSide(n1,n2,l);
        addSide(n2,n1,l);
    }
    dijs(s-1);
    for(int i=0;i<n;i++){
        if(i!=0)printf(" ");
        printf("%d",dist[i]);
    }
    printf("\n");
    for(int i=0;i<n;i++){
        if(i!=0)printf(" ");
        printf("%d",nvia[i]);
    }
    return 0;
}
posted @ 2021-05-31 22:59  荒于曦  阅读(177)  评论(0)    收藏  举报