[ACM] 训练赛题解
2021_积分赛第二场
名称 | 来源 | 算法 |
---|---|---|
敌兵布阵 | HDU-1166 | 线段树(简单) |
Emoticons | ICPC Central Russia Regional Contest (CRRC 19) | map 模拟 |
Power play | ICPC Central Russia Regional Contest (CRRC 19) | 浮点二分(卡精度) |
Prinzessin der Verurteilung | CodeForces 1536B | 思维题 |
Vases and Flowers | HDU 4614 | 线段树 + 二分 |
Omkar and Bad Story | CodeForces 1536A | 思维题 |
A - 敌兵布阵
题解
题意中文没啥好说的,多组测试样例,对输入的数组进行区间查询,单点修改,就是专门留给你们的线段树板子题。
#include <bits/stdc++.h> using namespace std; const int N = 50005; struct Node { int l,r; int sum; }T[N<<2]; int a[N]; void push_up(int rt) { T[rt].sum = T[rt << 1].sum + T[rt << 1|1 ].sum; } void build(int rt,int l,int r) { T[rt] = {l,r,0}; if( l == r ){ T[rt].sum = a[l]; return ; } int mid = (l + r) >>1; build(rt << 1,l,mid),build(rt << 1|1,mid + 1,r); push_up(rt); } void update(int rt,int pos ,int val) { if( T[rt].l == T[rt].r ){ T[rt].sum += val; return ; } int mid = (T[rt].l + T[rt].r) >>1; if( pos <= mid ) update(rt << 1,pos,val); else update( rt << 1|1,pos ,val ); push_up(rt); } int query(int rt,int l,int r) { if( l <= T[rt].l && r >= T[rt].r ){ return T[rt].sum; } int mid = ( T[rt].l + T[rt].r ) >> 1; int son = 0; if( l <= mid ) son += query(rt << 1,l,r); if( r > mid ) son += query(rt << 1|1,l,r); return son; } void solve(){ int n; cin >> n; for(int i = 1 ; i <= n ; i++){ cin >> a[i]; } build(1,1,n); string op; while( cin >> op ){ if( op == "End" ) break; int x,y; cin >> x >> y; if( op == "Query" ){ cout << query(1,x,y) <<"\n"; }else if( op == "Add" ){ update( 1,x,y ); }else{ update( 1,x,(-1)*y); } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int t; cin >>t; for(int i = 1 ; i <= t ; i++){ cout << "Case "<<i<<":"<< "\n"; solve(); } return 0; }
B - Emoticons
题意
一个叫 Basil 的程序员,正在写一个新的文本编辑器。为了查看邮件,Basil编写了一个特殊的模块,该模块从文本文件中提取所有表情符号,并将它们放在单独的行中显示。不幸的是,模块中出现了一个错误,表情符号中的字符混淆了。
Basil 知道以下的这些表情都应用到了信件中:
;-)
,;-(
,:)
,:(
,:-\
,:-P
:D
,:C
,:-0
,:-|
,8-0
,:-E
%0
,:-X
,:∼(
,[:|||:]
帮助 Basil, 写一个程序,把读入的乱序表情,恢复正常。如果有几种可能的恢复选项,那么其中任何一种都是正确的。
题解
就是一道大模拟题,用map来存每个字符出现了多少次。
由分析后我们可以知道,有的表情的组合优先级是高于其他表情的(也就是说,我们在组合的时候要优先去组合他)。
有的符号是独一无二的,他只要出现,就唯一对应一个表情,那么其他非唯一的表情就是肯定会给他优先消耗。就比如说
D
,C
,P
,8
... 只要这些字符出现,那么它就唯一的对应:D
,:C
... 这些表情,我们此时就把这个表情所涉及到的字符都在map中 -- ,就好了。 注:
\
字符,要两个才可以\\
,此为转义字符 注意:这段代码非常非常繁琐,模拟题就是来恶心人的,你wa了,就是细节错了,我们当时做的时候,他们都放弃了,我最后看了半天才发现那里错。
#include <bits/stdc++.h> using namespace std; const int N = 1e3 + 5; int a[N]; int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); string s; cin >> s; int len = s.size(); int cnt = 0; map<char, int> ma; ma['['] = 0, ma[':'] = 1; ma[']'] = 2, ma['|'] = 3; ma['D'] = 4, ma['C'] = 5; ma['P'] = 6, ma['E'] = 7; ma['8'] = 8, ma['0'] = 9; ma['%'] = 10, ma['X'] = 11; ma['~'] = 12, ma['\\'] = 13; ma['-'] = 14, ma['('] = 15; ma[';'] = 16, ma[')'] = 17; for(int i = 0; i < len; i ++ ){ a[ma[s[i]]] ++; } while(a[0]){ cout << "[:|||:]" << endl; a[0] --, a[1] -= 2, a[2] --; a[3] -= 3; } while(a[4]){ cout << ":D" << endl; a[4] --, a[1] --; } while(a[5]){ cout << ":C" << endl; a[5] --, a[1] --; } while(a[6]){ cout << ":-P" << endl; a[6] --, a[1] --, a[14] --; } while(a[7]){ cout << ":-E" << endl; a[14] --, a[1] --, a[7] --; } while(a[3]){ cout << ":-|" << endl; a[3] --, a[1] --, a[14] --; } while(a[8]){ cout << "8-0" << endl; a[8] --, a[14] --, a[9] --; } while(a[10]){ cout << "%0" << endl; a[9] --, a[10] --; } while(a[9]){ cout << ":-0" << endl; a[9] --, a[1] --, a[14] --; } while(a[11]){ cout << ":-X" << endl; a[11] --, a[1] --, a[14] --; } while(a[12]){ cout << ":~(" << endl; a[12] --, a[1] --, a[15] --; } while(a[13]){ cout << ":-\\" << endl; a[13] --, a[1] --, a[14] --; } while(a[14] && a[16] && a[15]){ cout << ";-(" << endl; a[16] --, a[14] --, a[15] --; } while(a[14] && a[16] && a[17]){ cout << ";-)" << endl; a[16] --, a[14] --, a[17] --; } while(a[1] && a[15]){ cout << ":(" << endl; a[1] --, a[15] --; } while(a[1] && a[17]){ cout << ":)" << endl; a[1] --, a[17] --; } cout << "LOL" << endl; return 0; }
C - Power play
题意
basil 在分析一个数学问题的时候发现一个有趣的现象, 对于数字 2,4 满足 $ 2^4 = 4^2$ 。他认为这对编程设计大赛的参赛者来说可能是一个巨大的挑战。不幸的是,Basil 不能发现另外的这样的数对了,他认为对于满足这种关系的数对,他只能找到2,4。好吧,那我们就改变条件,有三个数字。Basil 决定。编写的枚举选项程序证实了三个数字的任务是有意义的。
你的任务是:找到一个 x 其范围在 \(1 \le x\le1e^{18}\) ,满足 $ a^x = x^b$ (a,b为输入的数字),答案可能有很多任意输出满足条件的答案即可,如果没有这样的数存在那么输出0 。
题解
由题目我们得到这个公式
\[a^x = x^b \] 我们对两边取对数得到
\[x\times log(a) =b\times log(x) \] 移项可得
\[x = \frac{ b \times log(x) }{log(a)} \] 然后我们二分x的值,就可以了
单调性证明略
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL a, b; LL f(LL l, LL r) { if (l > r) return 0; LL mid = (l + r) / 2; double s=double(log(mid)/log(a)); if (abs(s * b - mid) < 0.00000000001) ///看是否有比他更小的存在 { LL ans = f(1, mid - 1); if (ans) return ans; return mid; } if (mid > s * b) return f(l, mid - 1); return f(mid + 1, r); } int main() { int flag = 1; scanf("%lld%lld", &a, &b); printf("%lld\n", f(1, 1e18)); }
\[ \]
D - Prinzessin der Verurteilung
题意
给你一个字符串,让你按字典序求第一个没在这个字符串中出现的子串,(由小到大排列的顺序为
a
b
c
d
e
f
...z
...aa
ab
ac
ad
... ),在样例qaabzwsxedcrfvtgbyhnujmiklop
中,a
到z
都 出现了,ab
出现了,ac
没有出现,那么答案就是ac
题解
因为数据范围不大,所以我们直接上 STL 就好了 ,
string
的find
函数可以解决 我们建立一个
vector<string >
数组来遍历的字典序字符串,然后对于每个字符串我们都在其后面26个字母 对于
""
空字符串,我们加26个字母后变成,a
,b
,c
,...,z
,对于a
字符串我们加了26个字符串后变成aa
,ab
,ac
,...,az
以此类推。#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 10; /// 遍历的模板 string str = "abcdefghijklmnopqrstuvwxyz"; void solve() { int n; cin >> n; string s; cin >> s; /// 建立字符串数组,同时把第一个字符串初始化为空字符串 vector<string> bfs = {""}; for(int i = 0; i < bfs.size() ; i++){ string te = bfs[i]; /// 找到了就直接输出 if( s.find(te) == string::npos ){ cout << te << "\n"; break; } /// 对于每个字符串,我们都在起后面加上26个字母 for( auto it : str ){ bfs.push_back(te+it); } } } int main() { ios::sync_with_stdio(false); cout.tie(nullptr); cin.tie(nullptr); int t; cin>> t; while(t--){ solve(); } }
E - Vases and Flowers
题意
给定一个整数n, 表示有n个花瓶(初始为空花瓶), 编号从0~n-1. 有如下两种操作:
①从编号为x的花瓶开始, 要放y朵花, 从前往后一次遍历, 如果是空花瓶则放一朵花在里面, 直至放完所有花或者遍历到最后一个花瓶为止. 倘若此时还有花放不下, 则将它们直接丢弃.
②清理[l, r]区间的所有花瓶, 如果里面有花则将其丢弃 对于每个操作①, 需要输出第一个放花的位置和最后一个放花的位置. 倘若一朵花都放不下, 需要输出"Can not put any one."
对于每个操作②, 需要输出该区间被清理的花的数量。题解
我们用线段树来维护这个花瓶,线段树的每一个结点,就相当于一个花瓶。
这道题最关键的点就是,如何设置状态,我们用
sum
来表示区间的剩余可插花数,用lazy来标记区间修改的操作lazy = -1,
为不变,lazy = 0
清除插花,lazy = 1
待插花。 对于操作①,我们在线段树上递归的进行二分,如果当前区间在我们需要插花的区间,并且
可插花数 <= 剩余的待插花树
,那么我们就在这个区间中二分的查找需要最左边的插花位置,和最右边的插花位置,并与全局变量 L,R 比较,最后输出这次的L,R。 对于操作②,相当于rangeQuery + rangeUpdate,在更新区间插花的同时,计数有那些结点插了花,并返回。
#include <bits/stdc++.h> using namespace std; const int N = 5e5 + 10; struct Node { int l,r; int sum,lazy; ///sum为当前剩余可插画数 ///lazy = -1,为不变,lazy = 0清除插花,lazy = 1待插花 }t[N << 2]; int L,R; void push_up(int rt){ t[rt].sum = t[rt << 1].sum + t[rt << 1|1].sum; } void build(int rt,int l, int r) { t[rt] = {l,r,1,-1}; if( l == r ) return ; int mid = (l + r) >> 1; build(rt << 1,l,mid),build(rt<<1|1,mid +1 ,r); push_up(rt); } void push_down(int rt) { if( t[rt].lazy == -1 ) return; int lazy = t[rt].lazy ,l = t[rt].l,r = t[rt].r; int mid = l + r >> 1; t[rt<<1].sum = lazy * (mid - l +1); t[rt<<1|1].sum = lazy * (r - mid); t[rt << 1|1].lazy = t[rt << 1].lazy = lazy; t[rt].lazy = -1; } ///查询插花区间最左边的位置 int findLeft(int rt) { if( t[rt].l == t[rt].r ) return t[rt].r; push_down(rt); if( t[rt << 1].sum != 0 ) findLeft(rt << 1); else findLeft(rt << 1|1); } ///查询插花区间最右边的位置 int findRight(int rt) { if( t[rt].l == t[rt].r ) return t[rt].l; push_down(rt); if( t[rt << 1|1].sum != 0 ) findRight(rt <<1|1); else findRight(rt << 1); } void rangeUpdate(int rt,int l,int r,int &val) { if( val == 0 || t[rt].sum == 0 ) return; if( l <= t[rt].l && r >= t[rt].r && t[rt].sum <= val ) { val -= t[rt].sum; /// 更新第一个和最后一个 L = min(L,findLeft(rt)) , R = max(R,findRight(rt)); t[rt].sum = 0; t[rt].lazy = 0; return ; } int mid = t[rt].l + t[rt].r >> 1; push_down(rt); if( l <= mid ) rangeUpdate(rt << 1,l,r,val); if( r > mid ) rangeUpdate(rt << 1|1,l,r,val); push_up(rt); } int rangeDelte(int rt,int l,int r) { if( l <= t[rt].l && r >= t[rt].r ) { int res = t[rt].r - t[rt].l + 1 - t[rt].sum; t[rt].sum = t[rt].r - t[rt].l + 1; t[rt].lazy = 1; return res; } int mid = (t[rt].l + t[rt].r) >> 1; int res = 0; push_down(rt); if( l <= mid ) res += rangeDelte(rt << 1,l,r); if( r > mid ) res += rangeDelte(rt << 1|1,l,r); push_up(rt); return res; } int main() { ios::sync_with_stdio(false); cin.tie(0); int n,t,m,x,y,cmd,te; cin >> t; while(t--) { cin >> n >> m; build(1,1,n); while(m--) { cin >> cmd >> x >> y; if(cmd == 1) { L = n + 1, R = 0,te = y; rangeUpdate(1,x+ 1,n,y); if(te == y) cout << "Can not put any one." <<endl; else cout << L - 1 << ' ' <<R - 1 <<endl; } else cout << rangeDelte(1,x + 1,y + 1) << endl; } cout <<endl; } return 0; }
F - Omkar and Bad Story
题意
Omkar收到了来自 Anton 的消息 "你对问题A的解释很混乱。再写个详细的说明 " 。正因如此, Omkar 给了你一个数组 a ,其有 n 个不同的数。如果一个数组(b)满足任意的两个元素 \(b_i,b_j\) ,\(|b_i - b_j|\) 在这个数组中至少出现了一次 ,那么这个数组就是一个nice 数组。此外,b中的所有元素必须是不同的。你能添加几个整数(可能是0)到a来创建一个大小不超过300的数组b 吗。如果a已经是个 nice数组了,你就不需要添加任何元素。
题解
如果原本数列中出现了负数,则直接输出NO,因为你有一个负数存在的情况下
假设原数列为1 和-1
-1 - 1的绝对值为2
-1 - 2的绝对值为3
可以看出新出现的数只会不断变大,所以是个无解保证数列为正数的情况下直接输出0至100就好了,
优化一下就是输出最大的数
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 10; void solve() { int n; cin>>n; int a[n]; bool sanu=false; int maxi=INT_MIN; for(int i=0; i<n; i++){ cin >> a[i]; maxi=max(maxi, a[i]); if(a[i] < 0) sanu = true; } if(sanu){ cout << "NO\n"; continue; }else { cout << "YES\n"; cout << maxi+1 << endl; for(int i = 0;i <= maxi ; i++) cout << i << " "; cout << endl; } } int main() { ios::sync_with_stdio(false); cout.tie(nullptr); cin.tie(nullptr); int t; cin>> t; while(t--){ solve(); } }