YNU第二届ICPC校赛题解

总览

难度算法/知识点First Blood出题人
A1-2数学 or 暴力枚举LihgOctal
B5Dijkstra+DP 或 分层图DijkstrafexlaFreya
C2排序、贪心LihgFelix
D401背包、贪心fexlaFelix
E4构造 or 模拟Octal
F3线性DP or 记忆化搜索JALJAYOFreya
G1循环语句cqsbz_cxyFelix
H1if语句JALJAYOFreya

这次比赛严重歪榜了,B D有人先做出来,F却到最后1h才有人AC

各题题解

A+B Problem

难度(暴力):入门(1)

难度(正解):普及-(2)

本题作者非常善良,没有卡 O ( n ) O(n) O(n)暴力算法(即循环区间内每一个数找另一个数的算法)

以下是 O ( 1 ) O(1) O(1)正解:

由第一个数的范围 [ a , b ] [a,b] [a,b]和两数之和 N N N,可以得到第二个数的范围必须为 [ N − b , N − a ] [N-b,N-a] [Nb,Na]

根据题意,第二个数还要在 [ c , d ] [c,d] [c,d]内,因此最终答案数是这两个区间的交集,即 [ m a x ( N − b , c ) , m i n ( N − a , d ) ] [max(N-b,c), min(N-a, d)] [max(Nb,c),min(Na,d)]中的整数个数

答案是 m i n ( N − a , d ) − m a x ( N − b , c ) + 1 min(N-a, d)-max(N-b,c)+1 min(Na,d)max(Nb,c)+1,可以 O ( 1 ) O(1) O(1)地求出一组答案。

C++题解:

#include<bits/stdc++.h>
using namespace std;
using ll = long long; //其实不开long long也能过
int main()
{
	ios::sync_with_stdio(false);
	int T; cin >> T;
	while(T--)
	{
		ll n; cin>>n;
		ll a,b,c,d; cin>>a>>b>>c>>d;
		ll dd = n - a;
		ll cc = n - b;
		ll ans = min(dd, d) - max(cc, c) + 1;
		cout << max(ans, 0ll) << "\n";
	}
	return 0;
}

Breath of the Wild

难度:提高+/省选(5)

简明题意:在带权有向图中寻找一条从起点到终点的最短路径,但是我们还需要考虑风弹技巧,即最多有K次机会可以让一条边的权重变为0。

我们可以使用 D i j k s t r a Dijkstra Dijkstra算法解决这个问题。 D i j k s t r a Dijkstra Dijkstra算法本身是用来求解单源最短路径问题的,一般情况下dis[i]维护从起点到i的距离,但在本题中,由于有风弹这么个恶心玩意儿,所以我们需要加一个维度,用dis[i][j] 维护从起点到i,还剩下j次风弹可以使用的最短距离。

我们用一个优先队列(小顶堆)来维护所有的状态,队列中的状态按照 dis 的值从小到大排列。

在每一步,我们从队列中取出一个状态,如果这个状态的 i是终点,那么 dis[i][j] 就是答案。否则,我们需要考虑从当前的塔i走到所有相邻的塔nxt的状态,有两种情况:

  1. 不使用风弹: dis[nxt][j]=dis[i][j]+a[i][j],其中a[i][j]ij的边权。

  2. 使用风弹: dis[nxt][j]=dis[i][j-1]

综合用以上两种情况更新dis[nxt][j],得到状态转移方程dis[nxt][j]=min({dis[nxt][j], dis[i][j]+a[i][j], dis[nxt][j]=dis[i][j-1]})(对, D i j k s t r a Dijkstra Dijkstra算法的本质就是DP!!!)

(也可以设dis[i][j]为从起点到i,使用了j次风弹的最短距离,状态转移的j-1j+1即可)

C++题解 By Fexla:

//
// Created by fexla on 2023/5/28.
//

#include <bits/stdc++.h>

using namespace std;
struct v {
    vector<int> nxt;
    vector<int> c;
};
typedef pair<int, int> pi;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout << setprecision(10) << fixed;
    int n, m, k, s, t;
    cin >> n >> m >> k >> s >> t;
    vector<v> g(n);
    for (int i = 0; i < m; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        g[a].nxt.push_back(b);
        g[a].c.push_back(c);
        g[b].nxt.push_back(a);
        g[b].c.push_back(c);
    }
    vector<vector<int>> dis(k + 1);
    for (int i = 0; i < k + 1; ++i) {
        dis[i] = vector<int>(n, 1e9);
    }
    dis[0][s] = 0;
    priority_queue<pi, vector<pi>, greater<pi>> q;
    for (int i = 0; i < g[s].nxt.size(); ++i) {
        int cost = g[s].c[i];
        int nxt = g[s].nxt[i];
        q.push({cost, nxt});
        if(k != 0) {
            q.push({0, 1e7 + nxt});
        }
    }
    while (!q.empty()) {
        auto item = q.top();
        q.pop();
        int kk = item.second / 10000000;
        int nxt = item.second % 10000000;
        int cost = item.first;
        if(dis[kk][nxt] <= cost) {
            continue;
        }
        dis[kk][nxt] = cost;
        for (int i = 0; i < g[nxt].nxt.size(); ++i) {
            int ccost = g[nxt].c[i];
            int nnxt = g[nxt].nxt[i];
            if(kk + 1 <= k && dis[kk + 1][nnxt] > cost) {
                q.push({cost, (kk + 1) * 10000000 + nnxt});
            }
            if(dis[kk][nnxt] > cost + ccost) {
                q.push({cost + ccost, kk * 10000000 + nnxt});
            }
        }
    }
    int ans = 1e9;
    for (int i = 0; i <= k; ++i) {
        ans = min(ans, dis[i][t]);
    }
    cout << ans;

    return 0;
}

Python题解:

import heapq

def solve(N, M, K, s, t, roads):
    graph = [[] for _ in range(N)]
    for a, b, c in roads:
        graph[a].append((b, c))
        graph[b].append((a, c))

    dist = [[float('inf')] * (K + 1) for _ in range(N)]
    dist[s][K] = 0

    pq = [(0, s, K)]
    while pq:
        d, node, k = heapq.heappop(pq)
        if node == t:
            return d
        if d > dist[node][k]:
            continue
        for neighbor, weight in graph[node]:
            if k > 0 and dist[neighbor][k - 1] > d:
                dist[neighbor][k - 1] = d
                heapq.heappush(pq, (d, neighbor, k - 1))
            if dist[neighbor][k] > d + weight:
                dist[neighbor][k] = d + weight
                heapq.heappush(pq, (d + weight, neighbor, k))

    return min(dist[t])

# 测试用例
n,m,k = map(int, input().split())
s,t = map(int, input().split())
road = []

for i in range(m):
    a,b,c = map(int, input().split())
    road.append((a,b,c))

print(solve(n,m,k,s,t,road))

Cells At Work

难度:普及-(2)

很鲜明的贪心,把细菌从多到少排序,依次消灭。用一个变量factor记录当前倍率,每消灭一种细菌,factor加倍并对MOD取模:factor=factor*2%MOD。排序后,消灭第i种细菌所用的细胞数为a[i]*factor,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

常见问题1:取模——必须每一步计算都取模,否则很容易爆long long,即使您使用Python,对大数的计算会很慢,会TLE。

多bb两句:如果没维护factor变量,每次使用 O ( l o g n ) O(logn) O(logn)的快速幂,最终复杂度是 O ( n l o g n l o g n ) O(nlognlogn) O(nlognlogn)。本来想卡掉这个复杂度,后来一想,会快速幂也是种本事,就给过了QwQ。

Python题解:

n = int(input())
MOD = 19260817
bac = list(map(int, input().split()))
bac.sort(reverse=True)

factor = 1
cell = 0

for i in bac:
    died = factor * i % MOD
    cell = (cell + died) % MOD
    factor = factor * 2 % MOD

print(cell)

C++题解:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD = 19260817;
ll a[2400000];
int n;
ll factor = 1, ans;
int main() 
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%lld", &a[i]);
	sort(a + 1, a + n + 1);
	reverse(a + 1, a + n + 1); //按从大到小排序
	for (int i = 1; i <= n; i++) 
	{
		ans = ans % MOD + a[i] * factor % MOD;
		factor = (factor << 1) % MOD; //等价于factor*2%MOD
	}
	printf("%lld\n", ans % MOD);
	return 0;
}

Discount Store

难度:普及-/提高(3)

解法:01背包(DP)+贪心

先把商品按价格排序。这样,从贵到便宜考虑每一件商品,如果购买则可“获得优惠券(0元购资格)”,即有资格领取一件后面的商品

f[i][j][0]代表考虑前i件物品、j元钱、没有优惠券,f[i][j][1]代表考虑前i件物品、j元钱、有优惠券

一个小小的贪心(也是本题难点):任何时候不会拥有2张优惠券(商品价格A>B>C>D,如果ABCD都想要,应该买A送B,买C送D;任何时候都不会原价买两件更贵的商品)

考虑以下两种情况下的状态转移:

当前没有优惠券:1. 考虑该商品之前有优惠券,用券免费换了 2. 之前没有优惠券,且不买该商品,因此 f[i][j][0] = max(f[i-1][j][0], f[i-1][j][1] + item[i].v);

当前有优惠券:1. 考虑该商品之前没有优惠券,原价购买商品 2. 之前有优惠券,且不买(兑换)该商品,因此 f[i][j][1] = max(f[i-1][j][1], f[i-1][j-item[i].w][0] + item[i].v) 。注意判断j要大于商品价格item[i].w,否则会数组越界。

C++题解:

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

const int MAXN = 505;
const int MAXW = 2005;

int n, W;

struct Item {
	int w, v;
}item[MAXN];

int f[MAXN][MAXW][2];

int main()
{
	cin >> n >> W;
	for (int i = 1; i <= n; i++)
		cin >> item[i].w >> item[i].v;
	memset(f, 0, sizeof(f));
	for (int i = 0; i <= W; i++)
		f[0][i][1] = -0x3f3f3f3f;

	sort(item+1, item+n+1, [](Item a, Item b){return a.w > b.w;});

	for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= W; j++)
		{
			f[i][j][0] = max(f[i-1][j][0], f[i-1][j][1] + item[i].v);
			f[i][j][1] = f[i-1][j][1];
			if(j >= item[i].w) {
				f[i][j][1] = max(f[i][j][1], f[i-1][j-item[i].w][0] + item[i].v);
			}
		}
	}
	cout << max(f[n][W][0], f[n][W][1]) << endl;
	return 0;
}

Exchange Card

难度:提高/省选-(4)

解法:构造 or 超级无敌大枚举

神仙题,我也写不出来QAQ,虽然没啥算法但是码量/思维量太大+一亿个细节呜呜呜呜呜。等出题大佬の官方题解。

出题人Octal官方题解:构造,分别考虑2个人换和3个人换的情况。

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
#define pcc pair<char, char>
typedef long long ll;
inline void solve()
{
    int n;
    cin >> n;
    map<pcc, vector<int>> a;
    vector<tuple<int, char, int, char>> ans;
    vector<string> s(n);
    for(int i=0; i<n; i++)
        cin >> s[i];
    //两人换
    for(int i=0; i<n; i++)
    {
        map<char, int> cnt;
        for(auto x : s[i])
            cnt[x] ++;
        vector<char> need, give;
        for(auto x : {'Y', 'N', 'U'})
        if(cnt[x] == 0)
            need.push_back(x);
        else
            for(int j=2; j<=cnt[x]; j++)
                give.push_back(x);
        for(int j=0; j<need.size(); j++)
        {
            char x = need[j];
            char y = give[j];
            if(!a[{y, x}].empty())
            {
                ans.emplace_back(i, y, a[{y, x}].back(), x);
                a[{y, x}].pop_back();
            }
            else a[{x, y}].push_back(i);
        }
    }
    //三人换
    char ch[3] = {'Y', 'N', 'U'};
    if(a[{ch[0], ch[1]}].empty())
        swap(ch[0], ch[1]);
    for(int i=0; i<a[{ch[0], ch[1]}].size(); i++)
    {
        int x = a[{ch[0], ch[1]}][i];
        int y = a[{ch[1], ch[2]}][i];
        int z = a[{ch[2], ch[0]}][i];
        ans.emplace_back(x, ch[1], y, ch[2]);
        ans.emplace_back(x, ch[2], z, ch[0]);
    }

    cout << ans.size() << endl;
    for(auto t : ans)
    {
        int a = get<0>(t);
        char b = get<1>(t);
        int c = get<2>(t);
        char d = get<3>(t);
        printf("%d %c %d %c\n", a+1, b, c+1, d);
    }
}
int main()
{
    int t;
    cin >> t;
    while(t--)
        solve();
    return 0;
}

Fine! Stop coding! Just go sleeping!

难度:普及/提高-(3)

(F题没人做,我和我的小伙伴们都惊呆了!这题比B D简单太多,反而难题有不少大佬过了……这次比赛严重歪榜了QAQAQAQAQ)

解法1:

线性DP,设dp[i]表示从第i分钟开始的最少工作时间,最终答案即为n-dp[1]

首先令dp数组全为正无穷,置dp[n+1]=0(注意:不能置dp[n]=0,因为可能第n分钟来一个1分钟完成的任务,这种情况下dp[n]=1),然后从n1枚举每分钟。

对于每一分钟考虑两种情况

  1. i分钟没有任务:dp[i]=dp[i+1]
  2. i分钟有任务 a 1 , a 2 , a 3 , . . . a_1,a_2,a_3,... a1,a2,a3,...,则对于每一个结束于 t t t分钟的任务 a i a_i ai,更新dp[i]dp[i]=min(dp[i], dp[t+1]+t-i+1)

C++题解:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int N{10005};
int n,k;
int dp[N]; //dp[i]表示从第i分钟开始的最少工作时间
//最后答案是n-dp[1]
vector<int> st_to_end[N];

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> k;
	memset(dp, 0x3f, sizeof dp);
	for(int i=1;i<=k;i++)
	{
		int st, len; cin >> st >> len;
		int en = st + len - 1;
		st_to_end[st].push_back(en);
	}
	int qwq = n;
	dp[n + 1] = 0;
	for(int i=n;i>=0;i--)
	{
		if(st_to_end[i].empty()) //第i分钟没有任务
		{
			dp[i] = dp[i + 1];
		}
		else
		{
			for(auto t : st_to_end[i])
			{
				int len = t - i + 1;
				dp[i] = min(dp[i], dp[t + 1] + len);
			}
		}
	}
	cout << n - dp[0] << endl;
	return 0;
}

解法2

状态转移和上面一样,只不过可以用记忆化搜素,从前往后枚举,更符合思维习惯。

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

constexpr int N{10005};
int n,k;
int dp[N];
vector<int> st_to_end[N];

int dfs(int x)
{
	if(dp[x]!=0x3f3f3f3f) return dp[x];
	if(x > n) return dp[x] = 0;
	if(st_to_end[x].size() == 0)
	{
		return dp[x] = dfs(x+1);
	}
	for(auto en : st_to_end[x])
	{
		dp[x] = min(dp[x], dfs(en+1) + en - x + 1);
	}
	return dp[x];
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> k;
	memset(dp, 0x3f, sizeof dp);
	for(int i=1;i<=k;i++)
	{
		int st, len; cin >> st >> len;
		int en = st + len - 1;
		st_to_end[st].push_back(en);
	}
	cout << n - dfs(0) << endl;
	for(int i=0;i<=n;i++)
		cout << dfs(i) << " ";
	return 0;
}

GPA Calculator

难度:入门(1)

循环读入每一门课,用一个变量total_score记录所有GP*学分之和(GP需要根据这门课的分数,用if-else计算),用另一个变量total_credit记录所有学分之和,最后total_score/total_credit就是答案。

C++题解:

#include<bits/stdc++.h>
using namespace std;
double gpa = 0, total_score = 0, total_credit = 0;

int main()
{
	int n; cin >> n;
	for(int i=1;i<=n;i++)
	{
		int score;
		double credit, gp;
		cin >> score >> credit;
		if(score >= 90)
			gp = 4;
		else if(score < 60)
			gp = 0;
		else
			gp = 1 + (score - 60) * 0.1;
		total_score += gp * credit;
		total_credit += credit;
	}
	gpa = total_score / total_credit;
	printf("%.2f", gpa);
	return 0;
}

Python题解:

n = int(input())
total_credit = 0
total_score = 0
for i in range(n):
    score, credit = map(float, input().split())
    if score > 90:
        gp = 4
    elif score < 60:
        gp = 0
    else:
        gp = 1 + (score - 60) * 0.1
    total_credit += credit
    total_score += credit * gp

gpa = total_score / total_credit
print("%.2f" % gpa)

Happy Birthday YNU

难度:入门(1)

用if-else判断年份 X X X,需要考虑5种情况:1921年及之前,1922年,1923年,1924年,1925年及之后。分别判断每一种情况做出相应输出即可。注意不要漏掉每一种情况

C++题解:

#include<iostream>
#include<cstdio>
using namespace std;

int main() {
	int n;
	scanf("%d",&n);
	n = n - 1923;
	if(n >= 0) {
		if(n == 0 || n == 1) printf("YNU is %d year old",n);
		else printf("YNU is %d years old",n);
	}
	else {
		if(n == -1) printf("YNU will be born after %d year",-n);
		else printf("YNU will be born after %d years",-n);
	}
}

Python题解:

x = int(input())

y = x - 1923
z = 1923 - x
if y >= 0:
    if y >= 2:
        print(f"YNU is {y} years old")
    else:
        print(f"YNU is {y} year old")
else:
    if z >= 2:
        print(f"YNU will be born after {z} years")
    else:
        print(f"YNU will be born after {z} year")
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值