A:Welcome to Yunnan!

【知识点】 数学、思维题

【idea】

假如 nnkk 的倍数,直接每个碟子放1个即可;

否则,由于每个碟子至少放一个,我们先在每个碟子内放一个

然后找到比n大的第一个k的倍数,把相差的这么多个均分到 nn 个碟子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
ll n, k;
cin >> n >> k;
if(n % k == 0) {
cout << 1 << endl;
} else {
ll sum = (n + k - 1) / k * k; //找到比n大的第一个k的倍数
ll r = sum - n;
cout << (r+n-1)/n + 1 << endl;
}
return 0;
}

B:Welcome come to YNU!

【知识点】递推

【idea】

ynuer出现的次数,可通过每一个r之前出现的ynue的个数累加求得;

同理,yune出现的次数…

其实本题也可以改为用一个数组来递推,但由于ynuer比较短,用五个变量即可达到目的;假如要求计数的字符串长度很长,也可以照样通过数组来递推。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;
const int mod = 1e9+7;
int main()
{
string s;
cin >> s;
ll y = 0; //y出现的个数
ll yn = 0; //yn出现的个数
ll ynu = 0; //ynu出现的个数
ll ynue = 0; //ynue出现的个数
ll res = 0; //ynuer出现的个数
for(char c : s) {
if(c == 'y') y ++;
if(c == 'n') yn = (yn + y) % mod;
if(c == 'u') ynu = (ynu + yn) % mod;
if(c == 'e') ynue = (ynue + ynu) % mod;
if(c == 'r') res = (res + ynue) % mod;
}
cout << res << endl;
return 0;
}

C:云上的气球派对

【知识点】桶思想

考察对一维数组的理解,与桶思想类似,典

【idea】

设立一个元素数组、一个地址数组

交换的时候两个数组的值都要改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;

int main()
{
int n, q;
cin >> n >> q;
vector<int> a(n+5), p(n+5);
//a[i]元素数组:代表第i个气球上的数字
//p[i]地址数组:代表写有i的气球所在位置编号
for(int i=1; i<=n; i++)
a[i] = p[i] = i;
while(q --)
{
int x;
cin >> x;
int p1 = p[x], p2; //p1记录x的原位置,p2记录x将前往的位置
if(p1 != n) p2 = p1+1;
else p2 = p1-1;
swap(a[p1], a[p2]);
p[a[p1]] = p1;
p[a[p2]] = p2;
}
for(int i=1; i<=n; i++)
cout << a[i] << " ";
return 0;
}

D:穿越之我是最伟大的卡牌大师!

【知识点】贪心算法

【idea】

本题改编自某一年ICPC亚洲区域赛(南京)的签到题

首先,要使p/q越大,q-1的次数越多越好。

我们先尽可能多的把0都当做-1使,如果走到后面发现前面q加的不够,再来矫正。

这里有个技巧,用一个tot变量来记录此前0->-1的数量,就可以用O(1)实现矫正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;
inline void solve()
{
int n;
scanf("%d", &n);

vector<int> a(n+5);
for(int i=1; i<=n; i++)
scanf("%d", &a[i]);
int p = 1, q = 1;
int tot = 0;
for(int i=1; i<=n; i++)
{
if(a[i] == 1) {
p ++, q ++;
}
if(a[i] == 0) {
if(q > 1) { //q还大于1,就把0当-1
q --;
tot ++;
} else {
p ++;
q ++;
}
}
if(a[i] == -1) {
q --;
if(q == 0) {
if(tot > 0) { //纠正一个0
q += 2;
p ++;
tot --;
continue;
}
puts("-1");
return;
}

}
}
int g = __gcd(p, q);
printf("%d %d\n", p/g, q/g);
}
int main()
{
int t;
t = 1;
while(t--)
solve();
return 0;
}

E:穿越之我要玩魔兽争霸!

【知识点】思维题

【idea】

首先,数据摆在那里了,这么大的数据,就不用考虑暴力模拟了。

这道题的过程虽然看着复杂,但是我们以宏观的视角去看这个问题,其实就没那么复杂。

需要重点关注的性质:x+ynx+y≤n

我们可以分两类情况讨论:

xyx≤y 则一定每秒都可以召唤水元素,即可召唤m个;

x>yx>y 时,一定不会超过魔法值上限,所以mm秒的总魔法值为 n+(m1)yn+(m-1)*y(因为最后一秒回复的魔法值是在召唤后所以用不到,故m1m-1),召唤一个水元素消耗xx点魔法,故召唤水元素为 minmin {m,(n+(m1)y)/xm, (n+(m-1)*y)/x}。(之所以要取min,是因为当n很大时,得出的值可能大于m)

综合可得,ans=min(m,(n+(m1)y)/x)ans=min(m,(n+(m-1)*y)/x)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;

int main()
{
ll n, m, x, y;
cin >> n >> m >> x >> y;
cout << min(m, ((m-1)*y+n)/x) << endl;
return 0;
}

Fun Fact:出这题的时候出题人不再善良@_@没有让暴力做法过。

我们知道普通cpu在1s时限内可以做10910^9次运算,但一般做算法题的时候我们都需要把程序的最大操作数控制在10810^8以内,甚至有些题是10710^7以内。

这是因为:一方面,一个算法都是会有常数的,在实际运行过程中,算法的常数会使实际操作数增大一个或多个数量级;另一方面测评机运行和本地运行不太一样,申请地址空间、调用编译好的文件啊都是需要费时的。所以说上限不是1e9。

但是验题的时候,octal发现O(1e9)的数据在C++20开O2优化的情况下竟然可以只用700ms,所以之后就更改了时限。

F:云山探险记

【知识点】背包 / dp+二分

【idea】

两种做法:一种是当做前缀背包的板子题来做;一种是做一个思维的转化,转为一道二分答案来写

第一种做法有点超纲,算是铜牌算法了,目前不要求掌握。这里提供第二种解法。

其实很简单,假如一个物品是 “必要” 的,那么所有勇气值比它大的物品也一定是必要的。

证明 :若 xx 是必要的,则存在集合 SS 使得 SS 去掉 xx<K<K,加上 xxK≥K。那么对于 SS 中的任意一个大于 xx 的数,显然也一定是必要的。对于集合外任意一个大于 xx 的数 yy ,也一定有 Sx+yS-x+y 满足条件。

所以,我们二分查找这个边界即可。

checkcheck 函数需要用到 DP,这个过程其实就等价于货币面值那个模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
int main()
{
int n, k;
cin >> n >> k;
vector<int> a(n+5);
for(int i=1; i<=n; i++)
scanf("%d", &a[i]);
sort(a.begin()+1, a.begin()+1+n);

auto check = [&](int x) {
vector<int> dp(k+5);
dp[0] = 1;
for(int i=1; i<=n; i++)
if(i != x) {
for(int j=k; j>=a[i]; j--)
dp[j] |= dp[j-a[i]];
}
for(int i=max(k-a[x], 0); i<=k-1; i++)
if(dp[i]) return true;
return false;
};
int l = 1, r = n+1;
while(l < r)
{
int mid = l+r >> 1;
if(check(mid)) r = mid;
else l = mid+1;
}
cout << n-l+1 << endl;
return 0;
}

G:黑白手

【知识点】博弈论、思维题

【idea】

n×nn × n 的棋盘是中心对称的,所以后手的最优策略就是模仿先手;而先手的最优策略是尽可能多地走过棋盘的格子。

在双方的相互施法下,最终一定是能将棋盘的每个格子都走过。

那么,棋盘的格子总数为奇数,就是先手获胜;反之,则后手胜。

1
2
3
4
5
6
7
8
9
10
11
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
if(n % 2) puts("J");
else puts("L");
return 0;
}

H:幸福在哪里

【知识点】暴力模拟 / 数学

作为签到题,本题数据范围很小,没有卡暴力做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
using namespace std;
#define int long long

signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int n,k;
cin>>n>>k;
string str="";
for(int i=0;i<26;i++){
for(int j=0;j<n;j++){
str+='A'+i;
}
}
cout<<str[k-1]<<"\n";
return 0;
}

当然,除了暴力模拟,也可以直接得到答案

(赛场上遇到这种范围的题,建议暴力做,肯定不会出错)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;

int main()
{
int n, k;
cin >> n >> k;
cout << (char)((k+n-1)/n + 'A' - 1) << endl;
return 0;
}

I:幸福终将降临在我们头上

【知识点】前缀数组+双指针算法 / 线段树 + 二分

【idea】

先想清楚一个结论:在一个序列中,假如要交换两数的位置,且只允许用类似冒泡排序交换方式,最少交换次数为两数之间的距离。

举例来说,我要交换 a[i]a[i]a[j]a[j] ,最少需要 ij|i-j| 次相邻交换。

接下来,我们再来想这道题要怎么做。

①第一步,先建立数学模型

我想让交换之后严格大于交换之前,是不是只要在 [k+1,n][k+1, n] 之中找一个数 xx ,再在 [1,k][1, k] 中找一个数 yy ,满足 x>yx > y,然后交换两数即可。

那么问题可以转化为,找一个区间 [L,R][L, R],使得区间包含 kk,并且 [k+1,R][k+1,R] 中 存在 xx , [L,k][L, k] 中存在 yy,满足 x>yx > y。求对于所有满足条件的区间,区间长度的最小值。

这种模型,一看就应该想到经典二分,可以二分区间长度然后 checkcheck 一下,关于如何找 x,yx, y ,可以考虑使用线段树。

当然,对于这道题来说,二分还是属于暴力的做法,但不失为一个好方法。

接下来介绍另一种转化,运用了前缀数组的思想。

因为这道题重要的是 kk 之前和 kk 之后的大小关系,但并不是所有的数都是重要的。在k前只有大数重要,在k后只有小数重要。

我们可以用前缀最值(后缀最值)来维护这些数。

我们用前缀最值(后缀最值)可以把目光集中在对两边数组的大小关系起决定性变化的几个数(也就是我们维护的后缀min前缀max)而忽视掉不大不小的那些无关紧要的数。

然后就是双指针算法的应用了。

我们要求最小值嘛,外循环来移左边界,不断地去缩小区间长度。

为了更好地理解双指针的过程,我们需要明白一个这个前缀数组(后缀数组)的特性:

k前面是单调递增的,k后面也是单调递增的。

O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;

int main()
{
int n, k;
cin >> n >> k;
vector<int> a(n+5);
for(int i=1; i<=n; i++)
cin >> a[i];
for(int i=k-1; i>=1; i--)
a[i] = min(a[i+1], a[i]); //前面找一个小的
for(int i=k+2; i<=n; i++)
a[i] = max(a[i-1], a[i]); //后面找一个大的
int res = 1e9;
for(int i=1, j=k+1; i<=k; i++)
{
while(j <= n && a[j] <= a[i])
j ++;
if(j <= n)
res = min(res, j - i);
}
if(res < 1e9) cout << res << endl;
else cout << -1 << endl;
return 0;
}

Lihg的本意是想考上面这个方法,但我验题的时候发现搓一个线段树也能过。

想着这道题能想到第一步转化,并且用线段树来实现也算很不错了,所以没有也并没有让出题人加强数据。

不过线段树方法属于暴力做法,时间复杂度还是很危险的,一不小心就会超时。

O(nlogn*logn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define pdd pair<double, double>
typedef long long ll;
const int N = 4e5+5;
int a[N];
struct node {
int maxv;
int minv;
} tr[N * 4];
void update(int id)
{
tr[id].maxv = max(tr[id*2].maxv, tr[id*2+1].maxv);
tr[id].minv = min(tr[id*2].minv, tr[id*2+1].minv);
}
void build(int id, int l ,int r)
{
if(l == r)
tr[id].maxv = tr[id].minv = a[l];
else {
int mid = l+r >> 1;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
//查询的区间为ql和qr max
int query1(int id, int l, int r, int ql, int qr)
{
if(l == ql && r == qr) return tr[id].maxv;
int mid = l+r >> 1;
if(qr <= mid) return query1(id*2, l, mid, ql, qr);
else if(ql > mid) return query1(id*2+1, mid+1, r, ql, qr);
else return max(query1(id*2, l, mid, ql, mid),
query1(id*2+1, mid+1, r, mid+1, qr));
}
//查询的区间为ql和qr min
int query2(int id, int l, int r, int ql, int qr)
{
if(l == ql && r == qr) return tr[id].minv;
int mid = l+r >> 1;
if(qr <= mid) return query2(id*2, l, mid, ql, qr);
else if(ql > mid) return query2(id*2+1, mid+1, r, ql, qr);
else return min(query2(id*2, l, mid, ql, mid),
query2(id*2+1, mid+1, r, mid+1, qr));
}
int main()
{
int n, k;
cin >> n >> k;
priority_queue<int> hp;
for(int i=1; i<=n; i++) {
cin >> a[i];
hp.push(a[i]);
}
build(1, 1, n);
set<int> b, c;
for(int i=1; i<=k; i++) {
b.insert(a[i]);
c.insert(hp.top());
hp.pop();
}
if(b == c) {
puts("-1");
return 0;
}
auto check = [&](int x) {
for(int i=k+1; i<=k+x; i++)
{
int R = min(n, i);
int L = max(1, i-x);
int minv = query2(1, 1, n, L, k);
int maxv = query1(1, 1, n, k+1, R);

if(maxv > minv)
return true;

}
return false;
};
int l = 1, r = n;
while(l < r) {
int mid = l+r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
return 0;
}