A:Welcome to Yunnan!
【知识点】 数学、思维题
【idea】
假如 n 是 k 的倍数,直接每个碟子放1个即可;
否则,由于每个碟子至少放一个,我们先在每个碟子内放一个
然后找到比n大的第一个k的倍数,把相差的这么多个均分到 n 个碟子
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; 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; ll yn = 0; ll ynu = 0; ll ynue = 0; ll res = 0; 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); for(int i=1; i<=n; i++) a[i] = p[i] = i; while(q --) { int x; cin >> x; int p1 = p[x], p2; 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 --; tot ++; } else { p ++; q ++; } } if(a[i] == -1) { q --; if(q == 0) { if(tot > 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+y≤n
我们可以分两类情况讨论:
① x≤y 则一定每秒都可以召唤水元素,即可召唤m个;
② x>y 时,一定不会超过魔法值上限,所以m秒的总魔法值为 n+(m−1)∗y(因为最后一秒回复的魔法值是在召唤后所以用不到,故m−1),召唤一个水元素消耗x点魔法,故召唤水元素为 min {m,(n+(m−1)∗y)/x}。(之所以要取min,是因为当n很大时,得出的值可能大于m)
综合可得,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时限内可以做109次运算,但一般做算法题的时候我们都需要把程序的最大操作数控制在108以内,甚至有些题是107以内。
这是因为:一方面,一个算法都是会有常数的,在实际运行过程中,算法的常数会使实际操作数增大一个或多个数量级;另一方面测评机运行和本地运行不太一样,申请地址空间、调用编译好的文件啊都是需要费时的。所以说上限不是1e9。
但是验题的时候,octal发现O(1e9)的数据在C++20开O2优化的情况下竟然可以只用700ms,所以之后就更改了时限。
F:云山探险记
【知识点】背包 / dp+二分
【idea】
两种做法:一种是当做前缀背包的板子题来做;一种是做一个思维的转化,转为一道二分答案
来写
第一种做法有点超纲,算是铜牌算法了,目前不要求掌握。这里提供第二种解法。
其实很简单,假如一个物品是 “必要” 的,那么所有勇气值比它大的物品也一定是必要的。
证明 :若 x 是必要的,则存在集合 S 使得 S 去掉 x 就 <K,加上 x 就≥K。那么对于 S 中的任意一个大于 x 的数,显然也一定是必要的。对于集合外任意一个大于 x 的数 y ,也一定有 S−x+y 满足条件。
所以,我们二分查找这个边界即可。
check 函数需要用到 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×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[j] ,最少需要 ∣i−j∣ 次相邻交换。
接下来,我们再来想这道题要怎么做。
①第一步,先建立数学模型
我想让交换之后严格大于交换之前,是不是只要在 [k+1,n] 之中找一个数 x ,再在 [1,k] 中找一个数 y ,满足 x>y,然后交换两数即可。
那么问题可以转化为,找一个区间 [L,R],使得区间包含 k,并且 [k+1,R] 中 存在 x , [L,k] 中存在 y,满足 x>y。求对于所有满足条件的区间,区间长度的最小值。
这种模型,一看就应该想到经典二分,可以二分区间长度然后 check 一下,关于如何找 x,y ,可以考虑使用线段树。
当然,对于这道题来说,二分还是属于暴力的做法,但不失为一个好方法。
接下来介绍另一种转化,运用了前缀数组的思想。
因为这道题重要的是 k 之前和 k 之后的大小关系,但并不是所有的数都是重要的。在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); } }
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)); }
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; }
|