第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)部分题解

本文集合了多种算法题目,包括双指针、博弈论、数据查询、字符串处理、数学模拟等多个方面,深入浅出地解析了解题思路和关键技巧,并提供了参考代码。通过这些实例,读者可以提升算法理解和应用能力。

比赛地址

个人题解

C 上进的凡凡

解题思路

双指针,相当于每次用指针 leftright 划分出单调不减的一个子数组,让右指针勇往直前。如果 right 碰到不符合条件的数了,就开始计算前面这一段里面的子数组总数。

计算方法就是等差数列求和。如,前面划出的一段里有 3 3 3 个数,那么它们能够组成的子数组数量为 1 + 2 + 3 1 + 2 + 3 1+2+3 个。

感谢有巨巨带我不然真的心态崩了 不知道哪个是签到题就在那边乱做

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

int a[maxn];
int main() {
    int n = readint();
    a[0] = -1;
    a[n + 1] = -1;
    for(int i = 1; i <= n; i++) a[i] = readint();
    LL ans = 0;
    int left = 1, right = 2;
    while (right <= n + 1) {
        if (a[right] < a[right - 1]) {  //如果现在开始不能构成不减序列
            int k = right - left;
            ans += 1LL * k * (k + 1) / 2;
            left = right;  //左指针移过来
            right++;  //右指针继续探索
        } else {
            right++;
        }
    }
    printf("%lld", ans);
    return 0;
}

D Seek the Joker I

解题思路

联想到bash博弈。bash博弈的规则是,有 n n n 个东西,两个人每次最多能抽 k k k 个,抽到最后一个东西的玩家获胜。其规律是, n m o d    ( k + 1 ) = 0 n \mod (k + 1) = 0 nmod(k+1)=0,后手胜,否则先手胜。

本题是修改版,规则是抽到最后一个东西的玩家输。即:抽到第 n − 1 n - 1 n1 个东西的玩家胜,因此直接套用 bash 博弈的结论。

博弈太难了555

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

int main() {
    int t = readint();
    while (t--) {
        int n = readint(), k = readint();
        if ((n - 1) % (k + 1) == 0) printf("ma la se mi no.1!");
        else printf("yo xi no forever!");
        printf("\n");
    }
    return 0;
}

F 成绩查询

解题思路

查询,注意数据存取的方式。详细见代码。

这才是我错过的签到题吧

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

struct student{
    char name[30];
    int grade;
    int sex;
    int id;
}s[maxn];

bool cmp(int a, int b) {  //输出按照字典序排序
    string s1 = s[a].name;
    string s2 = s[b].name;
    return s1 < s2;
}
int main() {
    int n = readint();
    unordered_map<string, int> name;
    vector<int> g[110];
    for(int i = 0; i < n; i++) {
        scanf("%s %d%d%d", s[i].name, &s[i].grade, &s[i].sex, &s[i].id);
        name[s[i].name] = i;
        g[s[i].grade].push_back(i);
    }
    int m = readint();
    while (m--) {
        int t = readint();
        if (t == 1) {
            char now[30];
            scanf("%s", now);
            int pos = name[now];
            printf("%d %d %d\n", s[pos].grade, s[pos].id, s[pos].sex);
        } else {
            int gg = readint();
            if (!g[gg].empty()) {
                sort(g[gg].begin(), g[gg].end(), cmp);
                for(int i = 0; i < g[gg].size(); i++) {
                    printf("%s\n", s[g[gg][i]].name);
                }
            }
        }
    }
    return 0;
}

H 数羊

解题思路

先打个 n n n 100 100 100 以内的表,发现 m m m 三个值的情况下 n ( n ≥ 2 ) n(n \ge 2) n(n2) 是有规律的, n = 1 n = 1 n=1 时特判一下即可。

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e9 + 10;
using namespace std;

const int mod = 998244353;
int readint() {int x; scanf("%d", &x); return x;}

LL quick(LL a, LL b) {  //快速幂
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res % mod;

}
int a[100][100];

int reg(int n, int m) {  //此处是打表的代码
    if (n == 1 && m == 0) return 2;
    if (n == 0 && m >= 0) return 1;
    if (m == 0 && n >= 2) return n + 2;
    if (a[n][m] != 0) return a[n][m];
    return a[n][m] = reg(reg(n - 1, m), m - 1);
}
int main() {
    int t = readint();
    while (t--) {
        LL n;
        scanf("%lld", &n);
        int m = readint();
        if (n == 1) printf("2");
        else {
            if (m == 0) printf("%lld", (n + 2) % mod);
            else if (m == 1) printf("%lld", (n << 1) % mod);
            else printf("%lld", (quick(2LL, n)) % mod);
        }
        printf("\n");
    }
    return 0;
}

I 买花

解题思路

等比数列求和,枚举 k k k 即可。

谁能想到这题 WA 了,输出有毒啊,考我视力orz

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

LL quick(LL a, LL b) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}
int main() {
    int t = readint();
    LL n;
    while (t--) {
        bool flag = false;
        cin >> n;
        for(int k = 2; k <= 15; k++) {
            if (n % (quick(2, 1LL * k) - 1) == 0) {  //如果求出来首项为整数
                flag = true;
                break;
            }
        }
        printf("%s\n", flag ? "YE5" : "N0");
    }
    return 0;
}

J 这是一题简单的模拟

解题思路

按照题目要求去判断路径是否合法,之后更新答案。

N N N 不大,使用邻接矩阵存边,方便询问两个点之间是否有连边。判断的时候统计访问过的城市个数。

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 310;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

int M[maxn][maxn];
int f[maxn][maxn];
bool vis[maxn];

void init() {
    memset(M, 0, sizeof(M));
    memset(vis, 0, sizeof(vis));
}
int main() {
    int n = readint(), m = readint();
    int a, b, ff;
    init();
    while (m--) {
        scanf("%d%d%d", &a, &b, &ff);
        M[a][b] = M[b][a] = 1;
        f[a][b] = f[b][a] = ff;
    }
    int k = readint();
    int ans = inf;
    while (k--) {
        memset(vis, 0, sizeof(vis));
        int pre = 0, sum = 0, cnt = 0;  //pre保存上一个顶点
        bool flag = true;
        int t = readint();
        t++;  //为后面方便判断,相当于加上最后一个顶点0
        while (t--) {
            int now;
            if (t == 0) now = 0;
            else now = readint();
            if (M[now][pre] == 0) {
                flag = false;
//                break;  //此处注意不能break,否则数据没有完全读进来
            } else {
                if (!vis[now] && now) {
                    vis[now] = 1;
                    cnt++;
                }
                sum += f[now][pre];
                pre = now;
            }
        }
        if (flag && cnt == n)  //路径合法
            ans = min(sum, ans);
    }
    printf("%d", ans == inf ? -1 : ans);
    return 0;
}

K 黑洞密码

解题思路

直接模拟,其中题意理解了半天,加减字母的规则是:如果碰到 z z z,那么它再加一,得到的是 B B B Z Z Z 下一位是 b b b

没想到很简便的写法,就硬写的。

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 310;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

char change(int k, char c) {  //字母c往后面推k位
    while (k) {  //k变成0的时候退出
        if (c >= 'a' && c <= 'z') {
            if (c + k > 'z') {
                k -= ('z' - c) + 1;
                c = 'B';
            } else {
                c += k;
                return c;
            }
        }
        if (c >= 'A' && c <= 'Z') {
            if (c + k > 'Z') {
                k -= ('Z' - c) + 1;
                c = 'b';
            } else {
                c += k;
                return c;
            }
        }
    }
    return c;
}

int main() {
    string s, s1, s2, ans;
    cin >> s;
    for(int i = 0; i < s.size(); i++) {
        if (isdigit(s[i])) s2.push_back(s[i]);
        else s1.push_back(s[i]);
    }
    ans = "";
    for(int i = 0; 4 * i + 3 < s1.size(); i++) {
        string tmp;
        for(int k = 4 * i; k <= 4 * i + 3; k++) {
            char now = change(s2[k] - '0', s1[k]);
            tmp.push_back(now);
        }
        for(int k = tmp.size() - 1; k >= 0; k--)
            ans.push_back(tmp[k]);
    }
    cout << ans;
    return 0;
}

L 建立火车站

解题思路

二分枚举站台之间的最大距离。

参考代码

#include<bits/stdc++.h>
#define VI vector<int>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;
const int mod = 998244353;

int readint() {int x; scanf("%d", &x); return x;}

LL a[maxn], dis[maxn];
int n, k;
bool check(LL len) {  //判断当前长度是否合法
     int cnt = 0;
     for(int i = 2; i <= n; i++) {
         if (dis[i] > len) {
             cnt += dis[i] / len;
             if (dis[i] % len == 0) cnt--;
         } 
     }
     return cnt <= k;
}
int main() {
    n = readint();
    k = readint();
    LL maxx = -1;
    a[0] = 0;
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }
    sort(a + 1, a + 1 + n);
    for(int i = 2; i <= n; i++) {
        dis[i] = a[i] - a[i - 1];
        maxx = max(maxx, dis[i]);
    }
    LL left = 1, right = maxx + 1;
    LL ans;
    while (left < right) {
        LL mid = (left + right) / 2;
        if (check(mid)) {
            ans = mid;
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    printf("%lld", ans);
    return 0;
}

补题

B 小宝的幸运数组

解题思路

先处理出前缀和数组 s s s,由 ( s [ i ] − s [ j ] ) % q = 0 (s[i] - s[j]) \% q = 0 (s[i]s[j])%q=0 得到 s [ i ] % q = s [ j ] % q s[i] \% q = s[j] \% q s[i]%q=s[j]%q。因此只要找距离最大的两个满足条件的前缀和即可。

采用在线处理的方式,更新答案。

参考代码

#include<bits/stdc++.h>
typedef long long LL;
typedef double db;
const int inf = 0x3f3f3f3f;
const LL INF = 1e18;
const int maxn = 1e5 + 10;
using namespace std;

int readint() {int x; scanf("%d", &x); return x;}

struct node{
    int start, len;
    node() {len = 0;}
}s[maxn];

bool vis[maxn];  //标记某个数的起点

LL pre[maxn];  //前缀和数组
void init() {
    memset(vis, 0, sizeof(vis));
    memset(s, 0, sizeof(s));
    pre[0] = 0;
}
int main() {
    int t = readint();
    while (t--) {
        init();
        int n = readint(), k = readint();
        int ans = -1;
        for(int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + 1LL * readint();
        }
        for(int i = 0; i <= n; i++) {
            LL x = pre[i];
            int res = x % k;
            if (!vis[res]) {
                vis[res] = true;
                s[res].start = i;
            } else {
                s[res].len = max(s[res].len, i - s[res].start);
                ans = max(s[res].len, ans);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值