PTA团体程序设计天梯赛篇(五)---- 难题篇一(30分题目)

数据结构类型

L3-002 特殊堆栈(树状数组)

题目链接

题目大意
本题的难点是维护一个动态的中值。

解题思路
因为值可能是不按大小顺序给出的,因此我们无法利用优先队列来维护,原因是在进行pop的时候可能弹出的是下边或者中间的值,而不是优先队列顶部的值。对于中值,我们对于每一个值如果出现一次,那么其次数加1,那么中值就转变为了这个次数序列中出现次数的中值(因为这个序列是单调的),那么可以单点修改与单点查询的数据结构,就是树状数组了。

  • 对于求中值,我们可以在0 ~ N 中进行二分。

代码:

#include<iostream>
#include<string>
#include<stack>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int t[N];
stack<int>st;
int n ,x;

int lowbit(int x){ return x & -x; }

void add(int k , int v){
    for( ; k < N ; k += lowbit(k)) t[k] += v;
}

int get(int n){
    int ans = 0;
    for( ; n ; n -= lowbit(n))ans += t[n];
    return ans;
}

int PeekMedian(){
    int l = 1 , k = (st.size() + 1)/2 , r = N - 1;
    while(l < r){
        int mid = (l + r)>>1;
        if(get(mid) >= k) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main(){
    cin>>n;
    while(n--){
        string s;
        cin>>s;
        if(s == "Pop"){
            if(st.size() == 0)cout<<"Invalid\n";
            else{
                x = st.top();
                cout<<st.top()<<endl;
                st.pop() , add(x , -1);
            }
        }else if(s == "Push"){
            cin>>x;
            st.push(x) ,add(x,1);
        }else{
            if(st.size() == 0)cout<<"Invalid\n";
            else cout<<PeekMedian()<<endl;
        }
    }
    return 0;
}

L3-003 社交集群(并查集)

题目链接

题目大意
题目是给出了每一个人的兴趣的编号的集合,对于存在一个兴趣相同的人我们认为其在一个圈子中。然后问你有多少兴趣圈,与每一个圈子有多少人。

解题思路
对于每一个人我们存下,其兴趣圈中的一个代表元素(不妨是第一个元素),之后将其兴趣圈中的所有值进行合并。对于没一个人都这样操作以后。我们枚举这n个人,我们

代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1100;

int p[N];
char ch;
int n , f[N];
int pos[N] ,cnt , p[N];

bool cmp(int a, int b){return a > b;}

void  init(){
    for(int i = 1 ;i  < N ; ++i) f[i] = i;
}

int find(int x){
    if(x == f[x])return x;
    else return f[x] = find(f[x]);
}

void join(int x,int y){
    x = find(x) , y = find(y);
    if(x != y)f[x] = y;
}

int main(){
    cin>>n;
    init();
    for(int i = 1 ; i <= n ; ++i){
        int k , y ;
        cin>>k>>ch;
        for(int j = 1 ;j <= k ; ++j){
            int x;
            cin>>x;
            if(j == 1)y = x , p[i] = y;
            else join(x ,y);
        }
    }
    for(int i = 1; i <= n ; ++i)
    {
         int x = find(p[i]);
         pos[x] ++;
     }
    sort(pos ,pos + N , cmp);
    for(int i = 0 ; pos[i] ; ++i)cnt++;
    cout<<cnt<<endl;
    for(int i = 0 ; i < cnt ; ++i)
    {
        if(i)cout<<" ";
        cout<<pos[i];
    }
    return 0;
}

搜索

L3-004 肿瘤诊断(三维bfs)

题目链接

解题思路
这可以说是一个三维bfs的板子题,这里x,y,z的顺序不重要只要你清楚就可以了。一般对于二维,三维的移动我们都会建立一个坐标变化的数组。我们在进行bfs的时候要确保每一个点只遍历一次。这里我们有两种方式确保只遍历一次。

确保bfs只遍历一次的方法

  1. 我们在每一次出队的时候将出队的元素置为1,为了确保遍历一次我们在置为1的上边先判断一下,如果该元素已经是1了就跳过。这样做可行的原因是,即使前后两个节点指向的节点又交集,但是由于队列的先进先出,如果我们在置为1之前跳过已经是1的点,也就是对于交集虽然都在队列中不过我们只会遍历先进去的节点,这样就确保了正确性。
    代码:
void bfs(int z ,int x ,int y){
    queue<node>q;
    q.push({z,x,y});
    while(q.size()){
        node u = q.front();
        q.pop();
        if(st[u.z][u.x][u.y])continue;
        s ++ ;     
        st[u.z][u.x][u.y] = 1;
        for(int i = 0 ;i < 6 ; ++i){
            int zz = u.z + dz[i] , xx = u.x + dx[i] , yy = u.y + dy[i];
            if(zz >= 0 && zz < L && xx >= 0 && xx < n  && yy >= 0 && yy < m && !st[zz][xx][yy] && str[zz][xx][yy] == 1)
                q.push({zz,xx,yy});
        }
    }
}
  1. 第二种是在将满足条件的节点压入队列的时候就置为1,这样是确保了在队列元素中的唯一性,显然能满足节点只遍历一次的情况。

代码:

void bfs(int z ,int x ,int y){
    queue<node>q;
    q.push({z,x,y});
    st[z][x][y] = 1;
    while(q.size()){
        node u = q.front();
        q.pop();
        s ++ ;     
        for(int i = 0 ;i < 6 ; ++i){
            int zz = u.z + dz[i] , xx = u.x + dx[i] , yy = u.y + dy[i];
            if(zz >= 0 && zz < L && xx >= 0 && xx < n  && yy >= 0 && yy < m && !st[zz][xx][yy] && str[zz][xx][yy] == 1)
            {
                q.push({zz,xx,yy});
                st[zz][xx][yy] = 1; // 在这里才不会重复加入
            }
        }
    }
}

代码:

#include <iostream>
#include <string>
#include <cstring>
#include<queue>
using namespace std;

const int N = 1300 , M = 130;
int n, m, L, t;
int str[M][N][M];
bool st[M][N][M];

int s, ans, dx[6] = {0, 0, 1, -1,0,0}, dy[6] = {1, -1, 0, 0, 0, 0},dz[6] = {0,0,0,0,1,-1};

struct node{
    int z,x,y;
};

void bfs(int z ,int x ,int y){
    queue<node>q;
    q.push({z,x,y});
  //  st[z][x][y] = 1;
    while(q.size()){
        node u = q.front();
        q.pop();
        if(st[u.z][u.x][u.y])continue;
        s ++ ;     
        st[u.z][u.x][u.y] = 1;
        for(int i = 0 ;i < 6 ; ++i){
            int zz = u.z + dz[i] , xx = u.x + dx[i] , yy = u.y + dy[i];
            if(zz >= 0 && zz < L && xx >= 0 && xx < n  && yy >= 0 && yy < m && !st[zz][xx][yy] && str[zz][xx][yy] == 1)
            {
                q.push({zz,xx,yy});
              //  st[zz][xx][yy] = 1; // 在这里才不会重复加入
            }
        }
    }
}


void solve() {
	memset(st, 0, sizeof st);
    for(int k = 0 ; k < L ; ++k)
	for (int i = 0 ; i < n ; ++i)
		for (int j = 0 ; j < m ; ++j)
			if (str[k][i][j] == 1 && !st[k][i][j]) {
				s = 0;
				bfs(k , i , j);
				if ( s >= t)
					ans += s;
			}
}


int main() {
	cin >> n >> m >> L >> t;
	for(int k = 0 ; k < L ; ++k) 
		for (int i = 0 ; i < n ; ++i)
			for (int j = 0 ; j < m ; ++j)
				cin>>str[k][i][j];
   solve();
	cout << ans << endl;
	return 0;

}

图论

L3-005 垃圾箱分布(多次SPFA)

题目链接

解题思路
对于这里首先我们要求的是垃圾箱到各个居名点的最短距离,这里有一个小技巧就是将垃圾箱也进行编号是 n + c ,c 是垃圾箱的编号,然后一起建边。之后每一次都以垃圾箱为起点进行spfa,大致步骤如下

分析:
这道题对每个垃圾点进行spfa即可,然后找到符合以下条件的垃圾点:

居民点与垃圾箱之间的最短距离不超过Dist
垃圾箱到居民点的最短距离最长
若符合2的不唯一,则选择平均距离最短的
若符合3的不唯一,则选择编号最小的

  • 对于对某一位数进行四舍五入的办法是,先将该数扩大倍数,使得要四舍五入的位数变成小数点的第一位,四舍五入之后再缩小相同倍数。

代码:

#include<iostream>
#include<queue>
#include<string>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;

const int N = 1e4 + 100 , M = 1e5 + 10;

int n , m , L,t;
int h[N] ,e[M] , ne[M] ,w[M] ,cnt;
bool st[N];
int d[N];

struct rub{
    int id  ;
    double mix ,avg;
    bool operator < (rub w){
        if(mix == w.mix && avg == w.avg)return id < w.id;
        else if(mix == w.mix)return avg < w.avg;
        else return mix > w.mix;
    }
}a[N];


void add(int u , int v ,int val){
    e[++cnt] = v , ne[cnt] = h[u] , w[cnt] = val , h[u] = cnt;
}

void spfa(int s){
    memset(st, 0 ,sizeof st);
    memset(d , 0x3f , sizeof d);
    queue<int>q;
    q.push(s);
    d[s] = 0;
    while(q.size()){
        int u = q.front();
        q.pop();
        st[u] = false;
        for(int i = h[u]; ~i ; i = ne[i]){
            int v = e[i];
            if(d[v] > d[u] + w[i]){
                d[v] = d[u] + w[i];
                if(!st[v]) q.push(v) , st[v] = true;
            }
        }
    }
}

int main(){
    memset(h,-1,sizeof h);
    cin>>n>>m>>L>>t;
    while(L--){
        string a ,b;
        int d , x,y;
        cin>>a>>b>>d;
        if(a[0] == 'G') x = n + atoi(a.substr(1).c_str());
        else x = atoi(a.c_str());
        if(b[0] == 'G') y = n + atoi(b.substr(1).c_str());
        else y = atoi(b.c_str());
        add(x,y,d) ,add(y,x,d);
    }
    int tot = 0 , mx ;
    for(int i = n + 1 ; i <= n + m ; ++i ){
        spfa(i);
        double sum = 0.0;
        mx = 0;
        a[tot] = {i - n ,1e10 , 0};
        for(int j = 1 ; j <= n ; ++j) a[tot].mix = min(a[tot].mix , d[j] + 0.0) , mx = max(mx , d[j]) , sum += d[j];
        if(mx > t)continue; // 有超过最大距离限制的就跳过
        a[tot++].avg = sum * 1.0 / (n + 0.0);
    }
    sort(a , a + tot); 
    if(!tot)cout<<"No Solution\n";
    else {
        printf("G%c\n",a[0].id + '0');
        printf("%.1lf %.1lf\n",a[0].mix , round(a[0].avg * 10.0) / 10.0 );
    }
    return 0;    
}

L3-007 天梯地图 (最短路+输出指定路径)

题目:题目链接
内容:

本题要求你实现一个天梯赛专属在线地图,队员输入自己学校所在地和赛场地点后,该地图应该推荐两条路线:一条是最快到达路线;一条是最短距离的路线。题目保证对任意的查询请求,地图上都至少存在一条可达路线。

输入格式:

输入在第一行给出两个正整数N(2 ≤ N ≤ 500)和M,分别为地图中所有标记地点的个数和连接地点的道路条数。随后M行,每行按如下格式给出一条道路的信息:

V1 V2 one-way length time

其中V1和V2是道路的两个端点的编号(从0到N-1);如果该道路是从V1到V2的单行线,则one-way为1,否则为0;length是道路的长度;time是通过该路所需要的时间。最后给出一对起点和终点的编号。

输出格式:

首先按下列格式输出最快到达的时间T和用节点编号表示的路线:
Time = T: 起点 => 节点1 => … => 终点

然后在下一行按下列格式输出最短距离D和用节点编号表示的路线:
Distance = D: 起点 => 节点1 => … => 终点

如果最快到达路线不唯一,则输出几条最快路线中最短的那条,题目保证这条路线是唯一的。而如果最短距离的路线不唯一,则输出途径节点数最少的那条,题目保证这条路线是唯一的。
如果这两条路线是完全一样的,则按下列格式输出:
Time = T; Distance = D: 起点 => 节点1 => … => 终点

解题思路
思路就是分别以time,length为权值跑两边Dijkstra,记住要用上堆优化。
我们用一个数组存路径,这个数组p[v]的含义是 指向 v 节点的是p[v]。同时为了找到正确的路径,我们还需要nc[v]表示到达v 节点走过的最小节点数量 , 与 fd[v]表示走到v的最短距离。

  • 测试点2的意思就是:最快的最短是距离最短而不是节点最少!
    所以说虽然你其他的测试点都过了,只是数据恰好距离最短和节点最少等效而已,你的代码还是存在问题的!
  • 对于检测路径是否一样,我们可以利用迭代来进行,从终点依次向前推,如果遇到不同的节点就返回false , 否则只到起点都是一样的那么就返回true。

代码:

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;

typedef pair<int ,int>PII;
const int N = 5100 , M = 5e5 + 10;

int n , m , v1 ,v2;
int h[N] , e[M] , ne[M] ,w1[M] ,w2[M] ,cnt;
bool st[N];
int dd[N] ,dt[N] ,pd[N],pt[N] ,nc[N] , fd[N];

void add(int u , int v ,int val1,int val2){
    e[++cnt] = v , ne[cnt] = h[u] , w1[cnt] = val1 , w2[cnt] = val2 , h[u] = cnt;
}

void dijstra1(int s){
    memset(dd , 0x3f ,sizeof dd);
    dd[s]  =  0 ;
    priority_queue<PII>heap;
    heap.push({0 , s});
    st[s] = true;
    while(heap.size()){
        int u = heap.top().second;
        heap.pop();
        for(int i = h[u] ;~i ; i = ne[i]){
            int v = e[i];
            if(dd[v] >= dd[u] + w2[i])
            {
                if(dd[v] > dd[u] + w2[i]){
                    dd[v] = dd[u] + w2[i] , pd[v] = u , fd[v] = fd[u] + w1[i];
                    if(!st[v]) heap.push({-dd[v] ,v}) , st[v] = true;
                }else if(fd[v] > fd[u] + w1[i]){
                     pd[v] = u ,fd[v] = fd[u] + w1[i];
                }
            }
        }
    }
}

void dijstra2(int s){
    memset(dt , 0x3f ,sizeof dt);
    dt[s] = 0;
    priority_queue<PII>heap;
    heap.push({0 , s});
    st[s] = true;
    while(heap.size()){
        int u = heap.top().second;
        heap.pop();
        for(int i = h[u] ;~i ; i = ne[i]){
            int v = e[i];
            if(dt[v] >= dt[u] + w1[i])
            {
                if(dt[v] > dt[u] + w1[i]){
                    dt[v] = dt[u] + w1[i] , pt[v] = u ,nc[v] = nc[u] + 1;
                    if(!st[v]) heap.push({-dt[v] ,v}) , st[v] = true;
                }else if(nc[v] > nc[u] + 1){
                    pt[v] = u , nc[v] = nc[u] + 1;
                }
            }
        }
    }
}

bool check(){
    int ed = v2 ;
    while(ed != v1){
        if(pd[ed] != pt[ed])return false;
        ed = pd[ed];
    }
    return true;
}

void dfs(int u , int p[]){
    if(u == v1)
    {
        cout<<v1;
        return ;
    }
    dfs(p[u] , p);
    cout<<" => "<<u;
}

int main(){
    memset(h , -1 ,sizeof h);
    cin>>n>>m;
    while(m--){
        int a,b,op , c,d;
        cin>>a>>b>>op>>c>>d;
        add(a,b ,c,d);
        if(!op)add(b,a,c,d);
    }
    cin>>v1>>v2;
    dijstra1(v1);
    memset(st , 0 ,sizeof st);
    dijstra2(v1);
    if(check()){
        printf("Time = %d; Distance = %d: ",dd[v2] ,dt[v2]);
        dfs(v2 ,pd);
    }else{
        printf("Time = %d: ",dd[v2]);
          dfs(v2 ,pd);
        puts("");
        printf("Distance = %d: ",dt[v2]);
          dfs(v2 ,pt);
    }
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落春只在无意间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值