天梯赛模拟赛2019-3-12 第二场

L2-014 列车调度 (25 分)

火车站的列车调度铁轨的结构如下图所示。

两端分别是一条入口(Entrance)轨道和一条出口(Exit)轨道,它们之间有N条平行的轨道。每趟列车从入口可以选择任意一条轨道进入,最后从出口离开。在图中有9趟列车,在入口处按照{8,4,2,5,3,9,1,6,7}的顺序排队等待进入。如果要求它们必须按序号递减的顺序从出口离开,则至少需要多少条平行铁轨用于调度?

输入格式:

输入第一行给出一个整数N (2 ≤ N ≤10​5​​),下一行给出从1到N的整数序号的一个重排列。数字间以空格分隔。

输出格式:

在一行中输出可以将输入的列车按序号递减的顺序调离所需要的最少的铁轨条数。

输入样例:

9
8 4 2 5 3 9 1 6 7

输出样例:

4

训练赛时写的时候没想到set这个方便的容器,用了数组结果只有18分

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e4 + 100;
const int inf = 0x3f3f3f3f;
const double ex = 1e-8;
set<int> a;

int main() {
    int n, x;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x;
        if (a.lower_bound(x) != a.end())
            a.erase(a.lower_bound(x));
        a.insert(x);
    }
    cout << a.size() << endl;
    return 0;
}

 

L2-013 红色警报 (25 分)

战争中保持各个城市间的连通性非常重要。本题要求你编写一个报警程序,当失去一个城市导致国家被分裂为多个无法连通的区域时,就发出红色警报。注意:若该国本来就不完全连通,是分裂的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.

输入样例:

5 4
0 1
1 3
3 0
0 4
5
1 2 0 4 3

输出样例:

City 1 is lost.
City 2 is lost.
Red Alert: City 0 is lost!
City 4 is lost.
City 3 is lost.
Game Over.

连通块问题,用并查集解决(虽然当时没用并查集A,就骗了一点分)

路径压缩之后当 i = fa [ i ] 时,则说明连通块加1,就可以求得连通块数目

没输入一个被攻陷得城市后判断一次当前连通块数目

如果连通块数组不变或者减1(原来就是孤立得一个),说明图连通块数目不受影响

如果连通块数目增加了1,则说明连通块加1,图中连通性受到影响

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e3 + 100;
const int inf = 0x3f3f3f3f;
const double ex = 1e-8;
int mapp[maxn][maxn];
int vis[maxn];
int s[maxn], e[maxn];
int n, m, k = 0;
int fa[maxn];

int Find(int x) {
    return fa[x] == x ? x : fa[x] = Find(fa[x]);
}

void Union(int x, int y) {
    int fx = Find(x), fy = Find(y);
    if (fx != fy) fa[fx] = fy;
}

int fun() { //求连通块数目
    for (int i = 0; i < n; i++) fa[i] = i;
    for (int i = 0; i < k; i++) {
        if (!vis[s[i]] && !vis[e[i]]) Union(s[i], e[i]);
    }
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        if (i == fa[i] && !vis[i]) cnt++;
    }
    return cnt;
}

int main() {
    cin >> n >> m;
    int u, v;
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &u, &v);
        if (!mapp[u][v]) {
            mapp[u][v] = mapp[v][u] = 1;
            s[k] = u;
            e[k++] = v;
        }
    }
    int cnt = fun();
    int q, c;
    cin >> q;
    while (q--) {
        cin >> u;
        vis[u] = 1;
        c = fun();
        if (c > cnt) printf("Red Alert: City %d is lost!\n", u);
        else printf("City %d is lost.\n", u);
        cnt = c;
        if (cnt == 0) printf("Game Over.\n");
    }
    return 0;
}

 

L2-016 愿天下有情人都是失散多年的兄妹 (25 分)

呵呵。大家都知道五服以内不得通婚,即两个人最近的共同祖先如果在五代以内(即本人、父母、祖父母、曾祖父母、高祖父母)则不可通婚。本题就请你帮助一对有情人判断一下,他们究竟是否可以成婚?

输入格式:

输入第一行给出一个正整数N(2 ≤ N ≤10​4​​),随后N行,每行按以下格式给出一个人的信息:

本人ID 性别 父亲ID 母亲ID

其中ID是5位数字,每人不同;性别M代表男性、F代表女性。如果某人的父亲或母亲已经不可考,则相应的ID位置上标记为-1

接下来给出一个正整数K,随后K行,每行给出一对有情人的ID,其间以空格分隔。

注意:题目保证两个人是同辈,每人只有一个性别,并且血缘关系网中没有乱伦或隔辈成婚的情况。

输出格式:

对每一对有情人,判断他们的关系是否可以通婚:如果两人是同性,输出Never Mind;如果是异性并且关系出了五服,输出Yes;如果异性关系未出五服,输出No

输入样例:

24
00001 M 01111 -1
00002 F 02222 03333
00003 M 02222 03333
00004 F 04444 03333
00005 M 04444 05555
00006 F 04444 05555
00007 F 06666 07777
00008 M 06666 07777
00009 M 00001 00002
00010 M 00003 00006
00011 F 00005 00007
00012 F 00008 08888
00013 F 00009 00011
00014 M 00010 09999
00015 M 00010 09999
00016 M 10000 00012
00017 F -1 00012
00018 F 11000 00013
00019 F 11100 00018
00020 F 00015 11110
00021 M 11100 00020
00022 M 00016 -1
00023 M 10012 00017
00024 M 00022 10013
9
00021 00024
00019 00024
00011 00012
00022 00018
00001 00004
00013 00016
00017 00015
00019 00021
00010 00011

输出样例:

Never Mind
Yes
Never Mind
No
Yes
No
Yes
No
No

dfs,奈何一直脑子转不过来dfs,唉

坑是可能查询某人父母,所以父母的性别也必须存下来

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e6 + 100;
const int inf = 0x3f3f3f3f;
const double ex = 1e-8;
struct node {
    char sex;
    int fa, ma, v;
} s[maxn];
int f;

void dfs(int k, int a, int b) {
    if (!f) return;
    if (a == -1 || b == -1) return;
    if (k > 5) return;
    if (a == b && k <= 5) {
        f = 0;
        return;
    }
    if (!s[a].v || !s[b].v) return;
    dfs(k + 1, s[a].fa, s[b].fa);
    dfs(k + 1, s[a].fa, s[b].ma);
    dfs(k + 1, s[a].ma, s[b].ma);
    dfs(k + 1, s[a].ma, s[b].fa);
}

int main() {
    int n;
    cin>>n;
    int id, fa, ma;
    char sex;
    for (int i = 1; i < maxn; i++) {
        s[i].v = 0;
    }
    for (int i = 1; i <= n; i++) {
        cin >> id >> sex >> fa >> ma;
        s[id].sex = sex;
        s[id].fa = fa;
        s[id].ma = ma;
        s[id].v = 1;
        if (fa != -1) s[fa].sex = 'M';
        if (ma != -1) s[ma].sex = 'F';
    }
    int k, a, b;
    cin >> k;
    while (k--) {
        cin >> a >> b;
        if (s[a].sex == s[b].sex) {
            puts("Never Mind");
            continue;
        }
        f = 1;
        dfs(1, a, b);
        if (f) puts("Yes");
        else puts("No");
    }
    return 0;
}

 

L3-010 是否完全二叉搜索树 (30 分)

将一系列给定数字顺序插入一个初始为空的二叉搜索树(定义为左子树键值大,右子树键值小),你需要判断最后的树是否一棵完全二叉树,并且给出其层序遍历的结果。

输入格式:

输入第一行给出一个不超过20的正整数N;第二行给出N个互不相同的正整数,其间以空格分隔。

输出格式:

将输入的N个正整数顺序插入一个初始为空的二叉搜索树。在第一行中输出结果树的层序遍历结果,数字间以1个空格分隔,行的首尾不得有多余空格。第二行输出YES,如果该树是完全二叉树;否则输出NO

输入样例1:

9
38 45 42 24 58 30 67 12 51

输出样例1:

38 45 24 58 42 30 12 67 51
YES

输入样例2:

8
38 24 12 45 58 67 42 51

输出样例2:

38 45 24 58 42 12 67 51
NO

完全二叉树,记住就好

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 1e6 + 100;
const int inf = 0x3f3f3f3f;
int tree[1 << 20], a;

void build(int x) {
    if (tree[x] == 0) {
        tree[x] = a;
    } else if (tree[x] < a) build(x << 1);
    else build(x << 1 | 1);
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a;
        build(1);
    }
    int f = 1, cnt = 1, k = 1;
    while (cnt <= n) {
        if (!tree[k]) f = 0;
        else {
            printf("%d", tree[k]);
            if (cnt++ != n) printf(" ");
        }
        k++;
    }
    if (f) printf("\nYES");
    else printf("\nNO");
    return 0;
}

 

L3-011 直捣黄龙 (30 分)

本题是一部战争大片 —— 你需要从己方大本营出发,一路攻城略地杀到敌方大本营。首先时间就是生命,所以你必须选择合适的路径,以最快的速度占领敌方大本营。当这样的路径不唯一时,要求选择可以沿途解放最多城镇的路径。若这样的路径也不唯一,则选择可以有效杀伤最多敌军的路径。

输入格式:

输入第一行给出 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

就是最普通的最短路,多加几个数组判断即可

不过中途不知道了写了什么bug,一遍又一遍的de,自闭

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 250;
int n, m, from, to;
string s1, s2;
map<string, int> p;
map<int, string> pp;
int num[maxn]; //记录敌人数量
int mapp[maxn][maxn];
int dis[maxn];
int pathnum[maxn]; //记录最短路径的数量
int contury[maxn]; //记录沿途城市数量
int enmy[maxn]; //记录杀敌数量
int path[maxn]; //记录路径,即记录每个点的上一个城市
int vis[maxn]; 
int ans[maxn]; 

void dijk() {
    memset(dis, 0x3f, sizeof(dis));
    memset(path, -1, sizeof(path));
    dis[from] = 0;
    pathnum[from] = 1;
    for (int i = 1; i < n; i++) {
        int p = 0, minn = 0x3f3f3f3f;
        for (int j = 1; j <= n; j++) {
            if (!vis[j] && dis[j] < minn) {
                minn = dis[j];
                p = j;
            }
        }
        vis[p] = 1;
        for (int j = 1; j <= n; j++) {
            if (dis[p] + mapp[p][j] < dis[j]) {
                dis[j] = dis[p] + mapp[p][j];
                path[j] = p;
                contury[j] = contury[p] + 1;
                enmy[j] = enmy[p] + num[j];
                pathnum[j] = pathnum[p];
            } else if (dis[p] + mapp[p][j] == dis[j]) {
                pathnum[j] += pathnum[p];
                if (contury[p] + 1 > contury[j]) {
                    contury[j] = contury[p] + 1;
                    path[j] = p;
                    enmy[j] = enmy[p] + num[j];
                } else if (contury[p] + 1 == contury[j] && enmy[p] + num[j] > enmy[j]) {
                    enmy[j] = enmy[p] + num[j];
                    path[j] = p;
                }
            }
        }
    }
}

void print() {
    int p = to;
    int k = 0;
    while (p != -1) {
        ans[k++] = p;
        p = path[p];
    }
    for (int i = k - 1; i >= 0; i--) {
        if (i == k - 1) cout << pp[ans[i]];
        else cout << "->" << pp[ans[i]];
    }
}

int main() {
    //freopen("in.txt", "r", stdin);
    cin >> n >> m >> s1 >> s2;
    p[s1] = 1;
    pp[1] = s1;
    from = 1;
    for (int i = 2; i <= n; i++) {
        cin >> s1 >> num[i];
        p[s1] = i;
        pp[i] = s1;
        if (s1 == s2) to = i;
    }
    memset(mapp, 0x3f, sizeof(mapp));
    int d;
    while (m--) {
        cin >> s1 >> s2 >> d;
        int u = p[s1], v = p[s2];
        mapp[u][v] = mapp[v][u] = d;
    }
    dijk();
    print();
    printf("\n%d %d %d\n", pathnum[to], dis[to], enmy[to]);
    return 0;
}

 

L3-012 水果忍者 (30 分)

2010年风靡全球的“水果忍者”游戏,想必大家肯定都玩过吧?(没玩过也没关系啦~)在游戏当中,画面里会随机地弹射出一系列的水果与炸弹,玩家尽可能砍掉所有的水果而避免砍中炸弹,就可以完成游戏规定的任务。如果玩家可以一刀砍下画面当中一连串的水果,则会有额外的奖励,如图1所示。

图 1

现在假如你是“水果忍者”游戏的玩家,你要做的一件事情就是,将画面当中的水果一刀砍下。这个问题看上去有些复杂,让我们把问题简化一些。我们将游戏世界想象成一个二维的平面。游戏当中的每个水果被简化成一条一条的垂直于水平线的竖直线段。而一刀砍下我们也仅考虑成能否找到一条直线,使之可以穿过所有代表水果的线段。

图 2

如图2所示,其中绿色的垂直线段表示的就是一个一个的水果;灰色的虚线即表示穿过所有线段的某一条直线。可以从上图当中看出,对于这样一组线段的排列,我们是可以找到一刀切开所有水果的方案的。

另外,我们约定,如果某条直线恰好穿过了线段的端点也表示它砍中了这个线段所表示的水果。假如你是这样一个功能的开发者,你要如何来找到一条穿过它们的直线呢?

输入格式:

输入在第一行给出一个正整数N(≤10​4​​),表示水果的个数。随后N行,每行给出三个整数x、y​1​​、y​2​​,其间以空格分隔,表示一条端点为(x,y​1​​)和(x,y​2​​)的水果,其中y​1​​>y​2​​。注意:给出的水果输入集合一定存在一条可以将其全部穿过的直线,不需考虑不存在的情况。坐标为区间 [−10​6​​,10​6​​) 内的整数。

输出格式:

在一行中输出穿过所有线段的直线上具有整数坐标的任意两点p​1​​(x​1​​,y​1​​)和p​2​​(x​2​​,y​2​​),格式为 x​1​​y​1​​x​2​​y​2​​。注意:本题答案不唯一,由特殊裁判程序判定,但一定存在四个坐标全是整数的解。

输入样例:

5
-30 -52 -84
38 22 -49
-99 -22 -99
48 59 -18
-36 -50 -72

输出样例:

-99 -99 -30 -52

凸包题

首先对于所有线段的上端点求一条不上凸的凸包使得上端点都在该凸包上面或位于凸包线段,对下端点做不下凸的凸包使得下端点都在该凸包下面或位于凸包线段

然后进行枚举

对上端点来说,每次取两个上端点,与所有的下端点都形成下凸则说明这两个上端点连接成的线段可以

下端点同理

代码:

#include <bits/stdc++.h>

using namespace std;
typedef unsigned long long ll;
const int maxn = 1e4 + 100;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int n;
struct node {
    int x, y;
} up[maxn], down[maxn];

bool cmp(node a, node b) {
    if (a.x != b.x) return a.x < b.x;
    return a.y < b.y;
}
int upd[maxn], downd[maxn];
//大于0是下凸,小于0是上凸
bool checkup(node a,node b,node c){
    return 1ll*(b.x-a.x)*(c.y-b.y)-1ll*(c.x-b.x)*(b.y-a.y)<0;
}
bool checkdown(node a,node b,node c){
    return 1ll*(b.x-a.x)*(c.y-b.y)-1ll*(c.x-b.x)*(b.y-a.y)>0;
}
int main() {
    //freopen("out.txt", "r", stdin);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> up[i].x >> up[i].y >> down[i].y;
        down[i].x = up[i].x;
    }
    sort(up + 1, up + n + 1, cmp);
    sort(down + 1, down + n + 1, cmp);
    int last1 = 1, last2 = 1, tot = n;
    for (int i = 2; i <= n; i++) {
        if (up[i].x == up[last1].x) {
            up[i].x = inf;
            up[last1].y = min(up[last1].y, up[i].y);
            tot--;
        } else {
            last1 = i;
        }
        if (down[i].x == down[last2].x) {
            down[i].x = inf;
            down[last2].y = max(down[last2].y, down[i].y);
        } else {
            last2 = i;
        }
    }
    sort(up + 1, up + n + 1, cmp);
    sort(down + 1, down + n + 1, cmp);
    if (tot == 1) {
        printf("%d %d %d %d\n", up[1].x, up[1].y, down[1].x, down[1].y);
        return 0;
    }

    int upnum = 0, downnum = 0;
    for (int i = 1; i <= tot; i++) {
        while(upnum>=2 && checkup(up[upd[upnum-2]],up[upd[upnum-1]],up[i]))
            upnum--;
        upd[upnum++]=i;
        while(downnum>=2 && checkdown(down[downd[downnum-2]],down[downd[downnum-1]],down[i]))
            downnum--;
        downd[downnum++]=i;
    }
    int flag=1;
    for(int i=0; i<upnum-1; i++){
        flag=1;
        for(int j=0; j<downnum; j++){
            if(checkup(up[upd[i]],down[downd[j]],up[upd[i+1]])){
                flag=0;
                break;
            }
        }
        if(flag){
            printf("%d %d %d %d\n",up[upd[i]].x,up[upd[i]].y,up[upd[i+1]].x,up[upd[i+1]].y);
            return 0;
        }
    }
    for(int i=0; i<downnum-1; i++){
        flag=1;
        for(int j=0; j<upnum; j++){
            if(checkdown(down[downd[i]],up[upd[j]],down[downd[i+1]])){
                flag=0;
                break;
            }
        }
        if(flag){
            printf("%d %d %d %d\n",down[downd[i]].x,down[downd[i]].y,down[downd[i+1]].x,down[downd[i+1]].y);
            return 0;
        }
    }

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值