PTA天梯20+数据结构

1、链表

(1)链表去重 伪链表 map+结构体的天坑题!

给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
在这里插入图片描述
【输入样例】
00100 5
99999 -7 87654
23854 -15 00000
87654 15 -1
00000 -15 99999
00100 21 23854
【输出样例】
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
思路: 用node{int num,string next}保存每个节点,注意输入个数可能>=100000!(多余的就不要了)也有可能地址-1之后还有多余数据!(循环时以!="-1"做判断条件)按出现与否分为link和depeat两个node数组,注意当有节点时才输出!

#define maxn 100000
#include <cmath>
#include <map>
struct node{int num;string next;};
int main(){
    string in,a,c;
    int n,b,c1=0,c2=0;
    cin>>in>>n;
    map<string,node> info;
    map<int,int> have;
    node res[maxn],defeat[maxn];
    for (int i=0;i<min(maxn,n);i++){
        cin>>a>>b>>c;
        info[a]={b,c};
    }
    if (n>=maxn){ //多余节点不要
        for (int i=maxn;i<=n;i++) cin>>a>>b>>c;
    }
    while (in!="-1"){ //不到表尾
        if (have.find(abs(info[in].num))==have.end()){//绝对值不重复
            res[c1++]={info[in].num,in};
            have[abs(info[in].num)] = 1;
        }
        else defeat[c2++]={info[in].num,in};
        in = info[in].next;
    }
    //结果输出
    if (c1){
        for (int i=0;i<c1-1;i++) cout<<res[i].next<<" "<<res[i].num<<" "<<res[i+1].next<<endl;
        cout<<res[c1-1].next<<" "<<res[c1-1].num<<" "<<-1<<endl;      
    }
    if (c2){
        for (int i=0;i<c2-1;i++) cout<<defeat[i].next<<" "<<defeat[i].num<<" "<<defeat[i+1].next<<endl;
        cout<<defeat[c2-1].next<<" "<<defeat[c2-1].num<<" "<<-1<<endl;
    }
    return 0;
}

(2)重排链表 map+结构体的伪链表题

在这里插入图片描述
【输入样例】
00100 6
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
【输出样例】
68237 6 00100
00100 1 99999
99999 5 12309
12309 2 00000
00000 4 33218
33218 3 -1
思路: map可以存储的伪链表,记得分奇偶个数结尾的链表就行。

#include <map>
struct node{
    int num;
    string next;
};
map<string,node> info;
int main(){
    string t,a,c;
    int n,b,cnt=0;
    cin>>t>>n;
    for (int i=0;i<n;i++){
        cin>>a>>b>>c;
        info[a]={b,c};
    }
    node link[n]; //链表存储
    while (t!="-1"){
        link[cnt++]={info[t].num,t};
        t=info[t].next;
    }
    int i=0,j=cnt-1;
    while (j-i>1){
        cout<<link[j].next<<" "<<link[j].num<<" "<<link[i].next<<endl;
        j-=1;
        cout<<link[i].next<<" "<<link[i].num<<" "<<link[j].next<<endl;
        i+=1;
    }
    if (cnt%2) //奇数个
        cout<<link[j].next<<" "<<link[j].num<<" "<<-1<<endl;
    else{//偶数个
        cout<<link[j].next<<" "<<link[j].num<<" "<<link[i].next<<endl;
        j-=1;
        cout<<link[i].next<<" "<<link[i].num<<" "<<-1<<endl;
    }
    return 0;
}

2、建树

(1)玩转二叉树 前序中序建树+层次队列输出

给定一棵二叉树的中序遍历和前序遍历,请你先将树做个镜面反转,再输出反转后的层序遍历的序列。所谓镜面反转,是指将所有非叶结点的左右孩子对换。这里假设键值都是互不相等的正整数。
【输入格式】
输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其中序遍历序列。第三行给出其前序遍历序列。数字间以空格分隔。
【输出格式】
在一行中输出该树反转后的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。
【输入样例】
7
1 2 3 4 5 6 7
4 1 3 2 6 5 7
【输出样例】
4 6 1 7 5 3 2
思路: 前序第一个为根节点,递归建树不要传数组传下标不然会段错误!不用反转层次先右后左就好了。

#include <bits/stdc++.h>
#define maxn 35
struct binode{//二叉树结构
    int data;
    struct binode *lchild,*rchild;
};
int pre[maxn],mid[maxn],n;
binode* preMid(int qian,int zhong,int n){//创建树
	if(n<=0) return NULL;
    int i;
    binode *t=(binode*)malloc(sizeof(binode));
    t->data=pre[qian]; //根节点
    for(i=0;mid[i+zhong]!=pre[qian];i++);
    t->lchild=preMid(qian+1,zhong,i);
    t->rchild=preMid(qian+i+1,zhong+i+1,n-i-1);
    return t;
}
int main(){
    cin>>n;
    for (int i=0;i<n;i++) cin>>mid[i];
    for (int i=0;i<n;i++) cin>>pre[i];
    binode *t =preMid(0,0,n); //建树
    //层次遍历:队列输出
    if (n>0){
        binode *q[maxn],*p;
        int head=0,tail=1;
        q[0]=t;
        while (head!=n-1){
            p=q[head++];
            cout<<p->data<<" "; //对当前节点的data进行
            if (p->rchild!=NULL) q[tail++]=p->rchild;  //右子节点入队
            if (p->lchild!=NULL) q[tail++]=p->lchild;  //左子节点入队
        }
        cout<<q[head++]->data;
    }
    return 0;
}

(2)树的遍历 中序后序建树+层次遍历队列输出

#include <bits/stdc++.h>
#define maxn 35
struct binode{//二叉树结构
    int data;
    struct binode *lchild,*rchild;
};
int post[maxn],mid[maxn],n;
binode *midEnd(int postl,int postr,int inl,int inr){
	binode *t=(binode*)malloc(sizeof(binode));
    if (postl>postr) return NULL; //结束
    t->data = post[postr];
    int i;
    for (i=inl;i<=inr;i++){
        if (mid[i]==post[postr]) break;//遍历到根节点
    }
    t->lchild = midEnd(postl,postl+i-inl-1,inl,i-1);
    t->rchild = midEnd(postl+i-inl,postr-1,i+1,inr);
    return t;
}
int main(){
    cin>>n;
    for (int i=0;i<n;i++) cin>>post[i];
    for (int i=0;i<n;i++) cin>>mid[i];
    binode *t =midEnd(0,n-1,0,n-1); //建树
    //层次遍历:队列输出
    if (n>0){
        binode *q[maxn],*p;
        int head=0,tail=1;
        q[0]=t;
        while (head!=n-1){
            p=q[head++];
            cout<<p->data<<" "; //对当前节点的data进行
            if (p->lchild!=NULL) q[tail++]=p->lchild;  //左子节点入队
            if (p->rchild!=NULL) q[tail++]=p->rchild;  //右子节点入队
        }
        cout<<q[head++]->data;
    }
    return 0;
}

3、树的特性

(1)完全二叉树的层次遍历 利用规律存储在数组里

二叉树如果每一个层的结点数都达到最大值就是完美二叉树。对于深度为 D 的、有 N 个结点的二叉树,若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点,这样的树就是完全二叉树。
给定一棵完全二叉树的后序遍历,请你给出这棵树的层序遍历结果。
【输入样例】
8
91 71 2 34 10 15 55 18
【输出样例】
18 34 55 71 2 10 15 91
思路: 如果完全二叉树按照从上到下,从左到右的从1开始顺序编号,则完全二叉树的左节点为当前节点编号x2,右节点为当前节点编号x2+1。建树用后序递归输入,输出使用遍历。

#define maxn 100
struct node{int num,l,r;};
node tree[maxn];
int n;
void build(int x){//完全二叉树建树
    if (x>n) return;
    build(2*x);//左子节点
    build(2*x+1);//右子节点
    cin>>tree[x].num; //中
}
int main(){
    cin>>n;
    build(1);//建树
    for (int i=1;i<n;i++) cout<<tree[i].num<<" ";
    cout<<tree[n].num;
}

(2)关于堆的判断 (利用完全二叉树的规律建小顶堆)

将一系列给定数字顺序插入一个初始为空的小顶堆H[]。随后判断一系列相关命题是否为真。命题分下列几种:
x is the root:x是根结点;
x and y are siblings:x和y是兄弟结点;
x is the parent of y:x是y的父结点;
x is a child of y:x是y的一个子结点。
【输入格式】
每组测试第1行包含2个正整数N(≤ 1000)和M(≤ 20),分别是插入元素的个数、以及需要判断的命题数。下一行给出区间[−10000,10000]内的N个要被插入一个初始为空的小顶堆的整数。之后M行,每行给出一个命题。题目保证命题中的结点键值都是存在的。
【输出格式】
对输入的每个命题,如果其为真,则在一行中输出T,否则输出F。
【输入样例】
5 4
46 23 26 24 10
24 is the root
26 and 23 are siblings
46 is the parent of 23
23 is a child of 10
【输出样例】
F
T
F
T

int a[1001],cnt=0,n,m,x,y;
void creat(int x){//建立小顶堆
    a[++cnt] = x;
    int t = cnt;
    while(t>1&&(a[t/2]>a[t])){//根节点小于等于子节点
        a[t] = a[t/2];
        a[t/2] = x;
        t /= 2;
    }
}
int main(){
    //输入
    string s;
    map<int,int> p;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>x;
        creat(x);
    }
    for(int i=1;i<=n;i++) p[a[i]]=i;//设置父亲节点
    for(int i=0;i<m;i++){
        cin>>x>>s;
        if(s[0]=='a'){//x y 是兄弟节点
            cin>>y;
            getline(cin,s);
            if(p[x]/2==p[y]/2) cout<<"T\n";
            else cout<<"F\n";
        }
        else{
            cin>>s; cin>>s;
            if (s[0]=='r'){//x是根节点
                if(p[x]==1) cout<<"T\n";
                else cout<<"F\n";
            }
            else if (s[0]=='p'){//x是y的父节点
                cin>>s; cin>>y;
                if(p[x]==p[y]/2) cout<<"T\n";
                else cout<<"F\n";
            }
            else{//x是y的一个子节点
                cin>>s; cin>>y;
                if(p[x]/2==p[y]) cout<<"T\n";
                else cout<<"F\n";
            }
        }
    }
    return 0;
}

(3)二叉搜索树的判定

一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点,
·其左子树中所有结点的键值小于该结点的键值;
·其右子树中所有结点的键值大于等于该结点的键值;
·其左右子树都是二叉搜索树。
所谓二叉搜索树的“镜像”,即将所有结点的左右子树对换位置后所得到的树。
给定一个整数键值序列,现请你编写程序,判断这是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。
【输入格式】:
第一行给出正整数 N(≤1000)。随后一行给出 N 个整数键值以空格分隔。
【输出格式】
如果输入序列是一棵二叉搜索树或其镜像进行前序遍历的结果,首先在一行中输出 YES ,在下一行输出该树后序遍历的结果。数字间有 1 个空格,一行的首尾不得有多余空格。若答案是否,则输出 NO。
【输入样例 1】
7
8 6 5 7 10 8 11
【输出样例 1】
YES
5 7 6 8 11 10 8
【输入样例 2】
7
8 10 11 8 6 7 5
【输出样例 2】
YES
11 8 10 7 5 6 8
【输入样例 3】
7
8 6 8 5 10 9 11
【输出样例 3】
NO
思路来:前序遍历特点是第一个为根节点,然后是左和右。二叉搜索树前序遍历特点是第一个节点(根节点)比左子树大,比右子树小。那么定义两个变量i、j分别从左到右遍历和从右向左,确定左儿子和右儿子区间。如果i-j=1肯定对,否则就不是前序遍历。镜像则左大右小判定

#include <vector>
#define maxn 1000
int pre[maxn],mirror=0;
vector<int> post;
void judge(int head,int tail){
    if (head>tail) return;
    int i=head+1,j=tail;
    if (!mirror){//左小右大
        while (i<=tail && pre[i]<pre[head]) i++; //左子树
        while (j>head && pre[j]>=pre[head]) j--; //右子树
    }
    else{
        while (i<=tail && pre[i]>=pre[head]) i++; //左子树
        while (j>head && pre[j]<pre[head]) j--; //右子树
    }
    if (i-j!=1) return; //非有序
    judge(head+1,j); //左子树
    judge(i,tail);//右子树
    post.push_back(pre[head]); //根节点入序列
}
int main(){
    //输入
    int n; cin>>n;
    for (int i=0;i<n;i++) cin>>pre[i];
    //先当左小右大进行判断
    judge(0,n-1);
    if (post.size()!=n){
        mirror = 1;
        post.clear();//清空镜像来一次
        judge(0,n-1);
    }
    if (post.size()!=n) cout<<"NO"; //非有序
    else{//有序
        cout<<"YES\n";
        for (int i=0;i<n-1;i++) cout<<post[i]<<" ";
        cout<<post[n-1];
    }
    return 0;
}

4、最小生成树/连通性(并查集)

(1)分而治之

在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序,判断每个方案的可行性。
【输入格式】
输入在第一行给出两个正整数 N 和 M(均不超过10 000),分别为敌方城市个数(于是默认城市从 1 到 N 编号)和连接两城市的通路条数。随后 M 行,每行给出一条通路所连接的两个城市的编号,其间以一个空格分隔。在城市信息之后给出参谋部的系列方案,即一个正整数 K (≤ 100)和随后的 K 行方案,每行按以下格式给出:
Np v[1] v[2] … v[Np]
其中 Np 是该方案中计划攻下的城市数量,后面的系列 v[i] 是计划攻下的城市编号。
【输出格式】
对每一套方案,如果可行就输出YES,否则输出NO。
【输入样例】
10 11
8 7
6 8
4 5
8 4
8 1
1 2
1 4
9 8
9 1
1 10
2 4
5
4 10 3 8 4
6 6 1 7 5 4 9
3 1 8 4
2 2 8
7 9 8 7 6 5 4 2
【输出样例】
NO
YES
YES
NO
NO

思路:并查集板子题,将每次攻破的城市做标记,判断剩下的是否联通。

struct node {int x,y;};
node road[10001];
int head[10001],visit[10001];
int f(int x){//找根节点
    if (head[x]==x) return x;
    return head[x]=f(head[x]);
}
void together(int x,int y){//并
    int a=f(x),b=f(y);
    if (a!=b) head[b]=a;
}
int main(){
    int n,m,k,t,in,flag;
    cin>>n>>m;
    for (int i=0;i<m;i++) cin>>road[i].x>>road[i].y;
    cin>>k;
    for (int i=0;i<k;i++){
        for (int j=1;j<=n;j++){//初始化
            head[j]=j;
            visit[j]=0;
        }
        cin>>t;
        flag=1;
        for (int j=0;j<t;j++){//标记已沦陷城市
            cin>>in;
            visit[in]=1;
        }
        for (int j=0;j<m;j++){//连通未攻陷城市
            if (visit[road[j].x]==0&&visit[road[j].y]==0) together(road[j].x,road[j].y);
        }
        for (int j=1;j<=n;j++){//判断是否攻破
            if (head[j]!=j){
                flag=0;
                break;
            }
        }
        if (flag) cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}

(2)红色警报

编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时发出红色警报。注意:若该国本来就不完全连通,是分裂的k个区域,而失去一个城市并不改变其他城市之间的连通性,则不要发出警报。
【输入格式】
第一行给出两个整数N(0 < N ≤ 500)和M(≤ 5000),分别为城市个数(于是默认城市从0到N-1编号)和连接两城市的通路条数。随后M行,每行给出一条通路所连接的两个城市的编号,其间以1个空格分隔。在城市信息之后给出被攻占的信息,即一个正整数K和随后的K个被攻占的城市的编号。
注意:输入保证给出的被攻占的城市编号都是合法的且无重复,但并不保证给出的通路没有重复。
【输出格式】
对每个被攻占的城市,如果它会改变整个国家的连通性,则输出Red Alert: City k is lost!,其中k是该城市的编号;否则只输出City k is lost.即可。如果该国失去了最后一个城市,则增加一行输出Game Over.。
【输入样例1】
5 4
0 1
1 3
3 0
0 4
5
1 2 0 4 3
【输出样例1】
City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.
【输入样例2】
8 10
7 1
3 7
1 2
2 3
2 5
2 6
2 0
5 6
5 0
6 0
4
4 2 0 7
【输出样例2】
City 4 is lost.
Red Alert: City 2 is lost!
City 0 is lost.
Red Alert: City 7 is lost!

思路:用node[]记录路径,先用并查集算一次连通性。每次攻城将visit[city]设为1,去掉关于此城市的边再计算连通性。若相邻两次计算时计数未增加则不警报否则警报,因为攻击城市号码不同所以最后判断所攻数量是否=城市数量即可判断是否完蛋。

struct node{int c1,c2;};
node road[5005];//道路
int head[505],city[505];//根节点,被攻城市
void init(int n){
    for (int i=0;i<n;i++) head[i]=i;
}
int getf(int x){
    if (head[x]==x) return x;
    return head[x]=getf(head[x]);
}
void together(int a,int b){
    int x=getf(a),y=getf(b);
    if (x!=y) head[x]=y;
}
int main(){
    //输入存储
    int n,m,a,b,c,cnt=0;
    cin>>n>>m;
    init(n);
    for (int i=0;i<m;i++){
        cin>>road[i].c1>>road[i].c2;
        together(road[i].c1,road[i].c2);
    }
    //计算连通性
    for (int i=0;i<n;i++){
        if (head[i]==i) cnt+=1;
        city[i]=0;
    }
    //开始攻城
    cin>>b;
    for (int j=0;j<b;j++){
        c=0;
        init(n);
        cin>>a;
        city[a]=1;
        for (int i=0;i<m;i++)
            if (!city[road[i].c1] && !city[road[i].c2])//不要这个城
                together(road[i].c1,road[i].c2);
        //重新计算连通性,被攻陷城市不算
        for (int i=0;i<n;i++)
            if (head[i]==i && !city[i]) c+=1;
        if (c<=cnt) cout<<"City "<<a<<" is lost.\n";
        else cout<<"Red Alert: City "<<a<<" is lost!\n";
        cnt = c; //地图缩小城市减少
    }
    if (b==n) cout<<"Game Over.\n"; //全部孤立
    return 0;
}

(3)社交集群 (集合交集+并查集)

当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。
【输入格式】
输入在第一行给出一个正整数 N(≤1000),为社交网络平台注册的所有用户的人数。于是这些人从 1 到 N 编号。随后 N 行,每行按以下格式给出一个人的兴趣爱好列表:
在这里插入图片描述
【输出格式】
首先在一行中输出不同的社交集群的个数。随后第二行按非增序输出每个集群中的人数。数字间以一个空格分隔,行末不得有多余空格。
【输入样例】
8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4
【输出样例】
3
4 3 1

思路:先用map[n]记录每个人的兴趣爱好,再通过双重循环找出有相同兴趣爱好的人,将其合并。最后统计圈子个数及每个圈子的人数,降序输出记得行末去掉多余空格。

//并查集板子
int f[1001],res[1001];
int getf(int x){
    return f[x]==x?x:f[x]=getf(f[x]);
}
void together(int x,int y){
    int a=getf(x),b=getf(y);
    f[b]=a;
}
int main(){
    //输入
    int n,in,a,cnt=0;
    cin>>n;
    char b;
    map<int,int> s[n];
    map<int,int>::iterator it;
    for (int i=0;i<n;i++){
        cin>>a>>b;
        for (int j=0;j<a;j++){
            cin>>in;
            s[i][in]=1;
        }
        f[i]=i;
        res[i]=0;
    }
    //集合交集判断
    for (int i=0;i<n;i++){
        for (int j=i+1;j<n;j++){
            map<int,int> p=s[i],q=s[j];
            for (it=p.begin();it!=p.end();it++){
                if (q.find(it->first)!=q.end()) break;//相同元素
            }
            if (it!=p.end())  together(i,j);//合并
        }
    }
    //结果处理输出
    for (int i=0;i<n;i++){
        a = getf(i);
        if (res[a]==0) cnt+=1; //统计不同圈子个数  
        res[a]+=1;
    }
    cout<<cnt<<endl;
    sort(res,res+n);
    for (a=n-1;a>=0&&res[a]>0;a--);//行末不得有空格
    for (int i=n-1;i>a+1;i--) cout<<res[i]<<" ";
    cout<<res[a+1];
    return 0;
}

(4)排座位 板子题(朋友+敌人)

【输入格式】
第一行给出3个正整数:N(≤100),即前来参宴的宾客总人数,则这些人从1到N编号;M为已知两两宾客之间的关系数;K为查询的条数。随后M行,每行给出一对宾客之间的关系,格式为:宾客1 宾客2 关系,其中关系为1表示是朋友,-1表示是死对头。注意两个人不可能既是朋友又是敌人。最后K行,每行给出一对需要查询的宾客编号。
这里假设朋友的朋友也是朋友。但敌人的敌人并不一定就是朋友,朋友的敌人也不一定是敌人。只有单纯直接的敌对关系才是绝对不能同席的
【输出格式】
对每个查询输出一行结果:如果两位宾客之间是朋友,且没有敌对关系,则输出No problem;如果他们之间并不是朋友,但也不敌对,则输出OK;如果他们之间有敌对,然而也有共同的朋友,则输出OK but…;如果他们之间只有敌对关系,则输出No way。
思路: 对于朋友的朋友是朋友这种传递关系使用并查集,而敌人并非传递关系所以使用图来存储直接相通的边即可。

#define maxn 101
int head[maxn],enemy[maxn][maxn];
//找根
int father(int x){
    if (head[x]==x) return x;
    return head[x]=father(head[x]);
}
//合并
int together(int x,int y){
    int a=father(x),b=father(y);
    if (a!=b) head[a]=b;
}
int main(){
    //输入及初始化
    int n,m,k,a,b,c;
    cin>>n>>m>>k;
    for (int i=1;i<=n;i++){
        head[i]=i;
        for (int j=1;j<=n;j++) enemy[i][j]=0;
    }
    for (int i=0;i<m;i++){
        cin>>a>>b>>c;
        if (c==1) together(a,b);
        else{ //敌人用图来表示,毕竟不传递
            enemy[a][b]=1;
            enemy[b][a]=1;
        }
    }
    for (int i=0;i<k;i++){
        cin>>a>>b;
        if (father(a)==father(b)){ //有朋友 有敌人吗
            if (enemy[a][b]==1) cout<<"OK but...\n";
            else cout<<"No problem\n";
        }
        else{ //非朋友 是敌人吗
            if (enemy[a][b]==1) cout<<"No way\n";
            else cout<<"OK\n";
        }
    }
    return 0;
}

(5)家庭房产:并查集后统计、排序输出

在这里插入图片描述
【输入样例】
10
6666 5551 5552 1 7777 1 100
1234 5678 9012 1 0002 2 300
8888 -1 -1 0 1 1000
2468 0001 0004 1 2222 1 500
7777 6666 -1 0 2 300
3721 -1 -1 1 2333 2 150
9012 -1 -1 3 1236 1235 1234 1 100
1235 5678 9012 0 1 50
2222 1236 2468 2 6661 6662 1 300
2333 -1 3721 3 6661 6662 6663 1 100
【输出样例】
3
8888 1 1.000 1000.000
0001 15 0.600 100.000
5551 4 0.750 100.000

思路:重点是并查集后的三次遍历!先把信息存储在struct中,同家族合并。

遍历使根节点加人数、房子和面积。
再遍历使同家族人数+1,根节点计算家庭个数。
再遍历算人均房子和面积。
排序后用%04d补齐编号 %.3f补齐小数点进行输出。

#include <algorithm>
#include <bits/stdc++.h>
#define maxn 100000
struct DATA{ //存储输入
    int id,fid,mid,num,area;
    int cid[10];
};
struct node{
    int id,people;
    double num,area;
    bool flag=false;//没有这个人
};
int head[maxn],visit[maxn];
DATA info[1005];
node res[maxn];
int father(int x){
    if (head[x]==x) return x;
    return head[x]=father(head[x]);
}
void together(int a,int b){
    int x=father(a),y=father(b);
    if (x>y) head[x]=y;
    else if (x<y) head[y]=x;
}
bool cmp(node a,node b){
    if (a.area==b.area) return (a.id<b.id);
    return (a.area>b.area);
}
int main(){
    int n,k,cnt=0;
    cin>>n;
    for (int i=0;i<maxn;i++){
        head[i]=i;
        visit[i]=0;
    }
    for (int i=0;i<n;i++){
        cin>>info[i].id>>info[i].fid>>info[i].mid>>k;
        visit[info[i].id]=1;
        if (info[i].fid!=-1){ //有父亲
            visit[info[i].fid]=1;
            together(info[i].id,info[i].fid);
        }
        if (info[i].mid!=-1){ //有母亲
            visit[info[i].mid]=1;
            together(info[i].id,info[i].mid);
        }
        for (int j=0;j<k;j++){
            cin>>info[i].cid[j];
            visit[info[i].cid[j]]=1;
            together(info[i].cid[j],info[i].id);
        }
        cin>>info[i].num>>info[i].area;
    }
    //给根节点加人加房子加面积
    for (int i=0;i<n;i++){
        int id=father(info[i].id);
        res[id].id=id;
        res[id].num+=info[i].num;
        res[id].area+=info[i].area;
        res[id].flag=true;
    }
    //算人数
    for (int i=0;i<maxn;i++){
        if (visit[i]) res[father(i)].people+=1; //家庭人数+1
        if (res[i].flag) cnt+=1;//家庭数+1
    }
    //算人均房子和面积
    for (int i=0;i<maxn;i++){
        if (res[i].flag){
            res[i].num = double(res[i].num*1.0/res[i].people);
            res[i].area = double(res[i].area*1.0/res[i].people);
        }
    }
    //排序并输出
    sort(res,res+maxn,cmp);
    cout<<cnt<<endl;
    for(int i=0;i<cnt;i++)
        printf("%04d %d %.3f %.3f\n",res[i].id,res[i].people,res[i].num,res[i].area);
    return 0;
}

5、 最短路径

(1)垃圾箱分布 dij用多次+sort排序输出

大家倒垃圾的时候,都希望垃圾箱距离自己比较近,但是谁都不愿意守着垃圾箱住。所以垃圾箱的位置必须选在到所有居民点的最短距离最长的地方,同时还要保证每个居民点都在距离它一个不太远的范围内。现给定一个居民区的地图,以及若干垃圾箱的候选地点,请你推荐最合适的地点。如果解不唯一,则输出到所有居民点的平均距离最短的那个解。如果这样的解还是不唯一,则输出编号最小的地点。
【输入格式】
输入第一行给出4个正整数:N(≤10^3)是居民点的个数;M(≤10)是垃圾箱候选地点的个数;K(≤10 ^4)是居民点和垃圾箱候选地点之间的道路的条数;D是居民点与垃圾箱之间不能超过的最大距离。所有的居民点从1到N编号,所有的垃圾箱候选地点从G1到GM编号。
随后K行,每行按下列格式描述一条道路:
P1 P2 Dist
其中P1和P2是道路两端点的编号,端点可以是居民点,也可以是垃圾箱候选点。Dist是道路的长度,是一个正整数。
【输出格式】
首先在第一行输出最佳候选地点的编号。然后在第二行输出该地点到所有居民点的最小距离和平均距离。数字间以空格分隔,保留小数点后1位。如果解不存在,则输出No Solution。
【输入样例1】
4 3 11 5
1 2 2
1 4 2
1 G1 4
1 G2 3
2 3 2
2 G2 1
3 4 2
3 G3 2
4 G1 3
G2 G1 1
G3 G2 2
【输出样例1】
G1
2.0 3.3
【输入样例2】
2 1 2 10
1 G1 9
2 G1 20
【输出样例2】No Solution

将所有垃圾箱作为第n+1 - 第n+m个点,用哈希表存转换函数。对每个垃圾箱进行n+m范围内的dij最短路径搜索:到某个居民点最短路径>d则此垃圾箱无效,否则记录到居民点的最短距离及距离之和,排序后输出结果。

#include <algorithm>
#include <iomanip>
#include <map>
struct node{
    int num;//编号
    double low,avge;//最短和平均距离
};
string str(int x){
    string r="";
    while (x){
        r=char(x%10+'0')+r;
        x/=10;
    }
    return r;
}
bool cmp(node a,node b){
    if (a.low==b.low){//最短距离一样长
        if (a.avge==b.avge){//平均距离一样长
            return (a.num<b.num);//编号最小
        }
        return (a.avge<b.avge);
    }
    return (a.low>b.low);
}
int main(){
    int n,m,k,d,c;
    cin>>n>>m>>k>>d;
    int e[n+m+1][n+m+1],dis[n+m+1],v[n+m+1];
    string a,b;
    map<string,int> h;//垃圾箱哈希转换表
    for (int i=1;i<=n+m;i++){//道路初始化
        for (int j=1;j<i;j++) e[i][j]=e[j][i]=1000000000;
        e[i][i]=0;
        if (i<=n) h[str(i)]=i;//居民点
        else h["G"+str(i-n)]=i;//垃圾点
    }
    while (k--){//输入边
        cin>>a>>b>>c;
        e[h[a]][h[b]]=e[h[b]][h[a]]=c;
    }
    if (m){//有垃圾桶
        //一个个垃圾箱计算
        node res[m];
        for (int i=1;i<=m;i++){
            double mini=1000000000,avg=0;//最小距离和平均距离
            //初始化
            int s=h["G"+str(i)];
            for (int j=1;j<=n+m;j++){
                dis[j]=e[s][j];
                v[j]=0;
            }
            //dij
            for (int j=1;j<=n+m;j++){
                int p=-1,q=1000000000;//最小距离
                for (int l=1;l<=n+m;l++){
                    if (!v[l]&&dis[l]<q){
                        p=l;
                        q=dis[l];
                    }
                }
                if (p==-1) break;//通不了了
                v[p]=1;
                for (int l=1;l<=n+m;l++){//更新距离表
                    if (!v[l]&&dis[l]>dis[p]+e[p][l]) dis[l]=dis[p]+e[p][l];
                }
            }
            int f=0;
            for (int j=1;j<=n;j++){
                if (dis[j]<mini) mini=dis[j];
                if (dis[j]>d) f=1; //到达不了所有居民点
                avg+=dis[j];
            }
            if (!f) res[i-1]={i,mini,avg/double(n)};
            else res[i-1]={n+m+i,0,1000000000};//无效点
            //if (!f)cout<<i<<" "<<mini<<" "<<avg/n<<endl;
        }
        sort(res,res+m,cmp);
        if (res[0].low==0) cout<<"No Sulution";
        else{
            cout<<"G"<<str(res[0].num)<<"\n";
            cout<<fixed<<setprecision(1)<<res[0].low<<" ";
            cout<<fixed<<setprecision(1)<<res[0].avge<<endl;
        }
    }
    else cout<<"No Solution";
    return 0;
}

(2)紧急救援 Dijkstra最短路径+最多数量+记录条数的板子题

地图上显示有多个分散的城市和一些连接城市的快速道路,并显示每个城市的救援队数量和每一条连接两个城市的快速道路长度。当其他城市有紧急求助电话时,任务是带领救援队尽快赶往事发地,同时一路上召集尽可能多的救援队。
【输入格式】
第一行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。
第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。
【输出格式】
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。
【输入样例】
4 5 0 3
20 30 40 10
0 1 1
1 3 2
0 3 3
0 2 2
2 3 2
【输出样例】
2 60
0 1 3
思路: 图Dijkstra计算最短路径、最大数量及打印路径的经典板子题

#include <iostream>
#define MAX 1000000000
#define maxn 505
using namespace std;
int e[maxn][maxn],v[maxn];//路径及是否走过
int dis[maxn],num[maxn],sum[maxn],cnt[maxn],pre[maxn];//距离,数量,总数,计数,前驱
void printf(int x){
    if (pre[x]==x){//根
        cout<<x;
        return;
    }
    printf(pre[x]);
    cout<<" "<<x;
    return;
}
int main(){
    //输入初始化
    int n,m,s,d,a,b,c;
    cin>>n>>m>>s>>d;
    for (int i=0;i<n;i++){
        for (int j=0;j<i;j++) e[i][j]=e[j][i]=MAX;
        e[i][i]=0;
    }
    for (int i=0;i<n;i++) cin>>num[i];
    while (m--){
        cin>>a>>b>>c;
        if (c<e[a][b]) e[a][b]=c,e[b][a]=c;
    }
    //Dij
    for (int i=0;i<n;i++){
        dis[i]=e[s][i];
        sum[i]=0;
        cnt[i]=0;
        pre[i]=i;
        v[i]=0;
    }
    sum[s]=num[s],cnt[s]=1;
    int maxi,mini;
    for (int i=0;i<n;i++){
        //寻找距离向量最短那个
        maxi=MAX,mini=-1;
        for (int j=0;j<n;j++){
            if (v[j]==0 && dis[j]<maxi) mini=j,maxi=dis[j];
        }
        if (mini==-1) break;//走不通
        v[mini]=1;//走过
        for (int j=0;j<n;j++){
            if (v[j]==0 && e[mini][j]+dis[mini]<dis[j]){//更短
                dis[j]=e[mini][j]+dis[mini];
                sum[j]=sum[mini]+num[j];
                cnt[j]=cnt[mini];
                pre[j]=mini;
            }
            else if (v[j]==0 && e[mini][j]+dis[mini]==dis[j]){//等长
                cnt[j]+=cnt[mini];
                if (sum[mini]+num[j]>sum[j]){//更多救援队
                    sum[j]=sum[mini]+num[j];
                    pre[j]=mini;
                }
            }
        }
    }
    cout<<cnt[d]<<" "<<sum[d]<<"\n";
    printf(d);
    return 0;
}

(3)天梯地图 最短时间中最短路径+最短路径中最少节点(用两次dij)

在这里插入图片描述
【输入样例1】
10 15
0 1 0 1 1
8 0 0 1 1
4 8 1 1 1
5 4 0 2 3
5 9 1 1 4
0 6 0 1 1
7 3 1 1 2
8 3 1 1 2
2 5 0 2 2
2 1 1 1 1
1 5 0 1 3
1 4 0 1 1
9 7 1 1 3
3 1 0 2 5
6 3 1 2 1
5 3
【输出样例1】
Time = 6: 5 => 4 => 8 => 3
Distance = 3: 5 => 1 => 3
【输入样例2】
7 9
0 4 1 1 1
1 6 1 3 1
2 6 1 1 1
2 5 1 2 2
3 0 0 1 1
3 1 1 3 1
3 2 1 2 1
4 5 0 2 2
6 5 1 2 1
3 5
【输出样例2】
Time = 3; Distance = 4: 3 => 2 => 5

思路就是典型dij,最短时间中最短距离:先按time排序再考虑dis;最短距离中最少节点:先dis排序再考虑num。记得输出前比较这俩路径(用vector直接==比较),相同和不同是两种输出形式。

#include <iostream>
#include <vector>
#define MAX 1000000000
#define N 501
using namespace std;
struct node{int len,time;};
node arr[N][N];
int T[N],D[N],v[N],tim[N],dis[N],num[N];
int s,d,mini,maxi;
vector<int> resT,resD;
void print_T(int x){//打印最短时间路径
    if (T[x]==x){
        resT.push_back(x);
        return;
    }
    print_T(T[x]);
    resT.push_back(x);
}
void print_D(int x){//打印最短距离路径
    if (D[x]==x){
        resD.push_back(x);
        return;
    }
    print_D(D[x]);
    resD.push_back(x);
}
int main(){
    int n,m,a,b,c,e,f;
    cin>>n>>m;
    for (int i=0;i<n;i++){//初始化
        for (int j=0;j<i;j++) arr[i][j]=arr[j][i]={MAX,MAX};
        v[i]=0;
        num[i]=1;
    }
    while (m--){//输入路径
        cin>>a>>b>>c>>e>>f;
        if (c==1) arr[a][b]={e,f};//单行
        else arr[a][b]=arr[b][a]={e,f};
    }
    cin>>s>>d;
    //dij_计算最短时间中最短路径
    for (int i=0;i<n;i++){
        tim[i]=arr[s][i].time;
        dis[i]=arr[s][i].len;
        T[i]=i;
    }
    for (int i=0;i<n;i++){
        mini=-1,maxi=MAX;
        for (int j=0;j<n;j++){
            if (!v[j]&&tim[j]<maxi){
                mini=j;
                maxi=tim[j];
            }
        }
        if (mini==-1) break;//走不通
        v[mini]=1;
        for (int j=0;j<n;j++){
            if (!v[j]&&tim[j]>tim[mini]+arr[mini][j].time){//最快
                tim[j]=tim[mini]+arr[mini][j].time;
                dis[j]=dis[mini]+arr[mini][j].len;
                T[j]=mini;
            }
            else if (!v[j]&&tim[j]==tim[mini]+arr[mini][j].time
                     &&dis[j]>dis[mini]+arr[mini][j].len){//最快中最短
                        dis[j]=dis[mini]+arr[mini][j].len;
                        T[j]=mini;
                     }
        }
    }
    //dij_最短路径中最少节点数
    for (int i=0;i<n;i++){
        v[i]=0;
        D[i]=i;
        dis[i]=arr[s][i].len;
    }
    for (int i=0;i<n;i++){
        mini=-1,maxi=MAX;
        for (int j=0;j<n;j++){
            if (!v[j]&&dis[j]<maxi){
                mini=j;
                maxi=dis[j];
            }
        }
        if (mini==-1) break;//走不通
        v[mini]=1;
        for (int j=0;j<n;j++){
            if (!v[j]&&dis[j]>dis[mini]+arr[mini][j].len){//最短
                dis[j]=dis[mini]+arr[mini][j].len;
                num[j]=num[mini]+1;
                D[j]=mini;
            }
            else if (!v[j]&&dis[j]==dis[mini]+arr[mini][j].len
                     &&num[j]>num[mini]+1){//相同长度,节点数更少
                        num[j]=num[mini]+1;
                        D[j]=mini;
                     }
        }
    }
    //输出
    resT.push_back(s);
    resD.push_back(s);
    print_T(d);
    print_D(d);
    if (resT==resD){//同一条路径,输出变化
        cout<<"Time = "<<tim[d]<<"; Distance = "<<dis[d]<<": ";
        for (int i=0;i<resT.size()-1;i++) cout<<resT[i]<<" => ";
        cout<<resT[resT.size()-1];
    }
    else{//不同路径分两行输出
        cout<<"Time = "<<tim[d]<<": ";
        for (int i=0;i<resT.size()-1;i++) cout<<resT[i]<<" => ";
        cout<<resT[resT.size()-1]<<endl;
        cout<<"Distance = "<<dis[d]<<": ";
        for (int i=0;i<resD.size()-1;i++) cout<<resD[i]<<" => ";
        cout<<resD[resD.size()-1];
    }
    return 0;
}

(4)直捣黄龙 最短路径中最多节点中最多数量

本题是一部战争大片 —— 你需要从己方大本营出发,一路攻城略地杀到敌方大本营。首先时间就是生命,所以你必须选择合适的路径,以最快的速度占领敌方大本营。当这样的路径不唯一时,要求选择可以沿途解放最多城镇的路径。若这样的路径也不唯一,则选择可以有效杀伤最多敌军的路径。
【输入格式】
输入第一行给出 2 个正整数 N(2 ≤ N ≤ 200,城镇总数)和 K(城镇间道路条数),以及己方大本营和敌方大本营的代号。随后 N-1 行,每行给出除了己方大本营外的一个城镇的代号和驻守的敌军数量,其间以空格分隔。再后面有 K 行,每行按格式城镇1 城镇2 距离给出两个城镇之间道路的长度。这里设每个城镇(包括双方大本营)的代号是由 3 个大写英文字母组成的字符串。
【输出格式】
按照题目要求找到最合适的进攻路径(题目保证速度最快、解放最多、杀伤最强的路径是唯一的),并在第一行按照格式己方大本营->城镇1->…->敌方大本营输出。第二行顺序输出最快进攻路径的条数、最短进攻距离、歼敌总数,其间以 1 个空格分隔,行首尾不得有多余空格。
【输入样例】
10 12 PAT DBY
DBY 100
PTA 20
PDS 90
PMS 40
TAP 50
ATP 200
LNN 80
LAO 30
LON 70
PAT PTA 10
PAT PMS 10
PAT ATP 20
PAT LNN 10
LNN LAO 10
LAO LON 10
LON DBY 10
PMS TAP 10
TAP DBY 10
DBY PDS 10
PDS PTA 10
DBY ATP 10
【输出样例】
PAT->PTA->PDS->DBY
3 30 210

这里输入是string,所以利用map进行哈希转换(毕竟用的是数字的板)然后要表:dis距离、sum人数和、road路径条数、cnt经过节点数、pre节点前缀及visit访问矩阵。令起点为0号,则初始化0节点各表数值后循环n次dij算法即可。

#include <iostream>
#include <map>
#define MAX 100000000
using namespace std;
//互相转换的哈希表
map<string,int> h;
map<int,string> to;
int pre[201];//前缀
string s,d;
void print(int x){//打印结果
    if (pre[x]==x){
        cout<<to[x]<<"->";
        return;
    }
    print(pre[x]);
    if (to[x]!=d) cout<<to[x]<<"->";
    else cout<<d<<endl;
}
int main(){
    int n,k,b;
    string a,c;
    cin>>n>>k>>s>>d;
    int e[n][n],num[n];//路径及各点人数
    int sum[n],dis[n],cnt[n],v[n],road[n];//人数和,距离和,节点和,访问,条数和
    h[s]=0; to[0]=s; num[0]=0;
    for (int i=1;i<n;i++){//输入每个点数量
        cin>>a>>b;
        h[a]=i;
        to[i]=a;
        num[i]=b;
    }
    for (int i=0;i<n;i++){//初始化
        for (int j=0;j<i;j++) e[i][j]=e[j][i]=MAX;
        e[i][i]=0;
    }
    for (int i=0;i<k;i++){//输入道路
        cin>>a>>c>>b;
        if (e[h[a]][h[c]]>b) e[h[a]][h[c]]=e[h[c]][h[a]]=b;
    }
    //起点初始化
    for (int i=0;i<n;i++){
        dis[i]=e[0][i];
        pre[i]=i;
        sum[i]=0;
        road[i]=0;
        cnt[i]=0;
        v[i]=0;
    }
    cnt[0]=1;
    road[0]=1;
    //dij
    int mini,maxi;
    for (int i=0;i<n;i++){
        mini=-1,maxi=MAX;//dis表中最短点
        for (int j=0;j<n;j++){
            if (!v[j]&&dis[j]<maxi){
                mini=j;
                maxi=dis[j];
            }
        }
        if (mini==-1) break;//走不通
        v[mini]=1;
        for (int j=0;j<n;j++){
            if (!v[j]&&dis[mini]+e[mini][j]<dis[j]){//更短
                dis[j]=dis[mini]+e[mini][j];
                road[j]=road[mini];
                sum[j]=num[j]+sum[mini];
                cnt[j]=cnt[mini]+1;
                pre[j]=mini;
            }
            else if (!v[j]&&dis[mini]+e[mini][j]==dis[j]){//等长
                road[j]+=road[mini];//条数增加
                if (cnt[j]<cnt[mini]+1){//解放城市个数更多
                    cnt[j]=cnt[mini]+1;
                    sum[j]=num[j]+sum[mini];
                    pre[j]=mini;
                }
                else if (cnt[j]==cnt[mini]+1&&sum[j]<num[j]+sum[mini]){//节点个数相同,杀敌军更多
                    sum[j]=num[j]+sum[mini];
                    pre[j]=mini;
                }
            }
        }
    }
    //输出
    print(h[d]);
    cout<<road[h[d]]<<" "<<dis[h[d]]<<" "<<sum[h[d]];
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值