镇楼
Update 12.3 11:13
A:再见603(贪心)
题意:
给n个字符串,可以任意两两拼接无限次,最多能拼多少个“603”
思路:
由样例可知,603串必须仅包含603串,如6031、9603等都是不合法的;
因此,记录603的所有子串“603”,“60”,“03”,“6”,“0”,“3”;
然后进行贪心,优先选择用最长的串:
ans += cnt("603");
ans += min(cnt("60"), cnt("3"));
ans += min(cnt("6"), cnt("03"));
ans += min({cnt("6"), cnt("0"), cnt("03")});
计算的时候记得将已经统计的串去除,cnt为该串出现的次数,可以使用map,哈希等方式保存。
时间复杂度O(nlog2n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { map<string, int> mp; int n; cin>>n; for(int i=1; i<=n; ++i) { string s; cin>>s; mp[s]++; } i64 ans = 0; ans += mp["603"]; int mn = min(mp["60"], mp["3"]); ans += mn; mp["60"] -= mn; mp["3"] -= mn; mn = min(mp["6"], mp["03"]); ans += mn; mp["6"] -= mn; mp["03"] -= mn; mn = min({mp["6"], mp["0"], mp["3"]}); ans += mn; cout<<ans<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1;// cin>>t; while (t--) solve(); return 0; }
B:欢迎加入实验室(构造+贪心)
题意:
给定一个长度 n 和一个 k ;
我们必须构造一个长度为 n 的序列 a(序列的定义为 1~n 当且仅当出现一次),并且,该序列 |a[i] - i| >= k。
并且使字典序尽可能小。
思路:
答案唯一,一眼贪心。
当 k > n/2 时:
显然数组中间的数无论如何也没法满足|a[i] - i| >= k,直接输出-1。
当 k < n/2 时:
我们根据贪心发现,当 n >= 4*k时,(n, k)可以转移为(n - k*2, k);
例如:
9 2 5 2
3 4 1 2 7 8 9 5 6 -> 7 8 9 5 6
由于前2*k位满足贪心的最小字典序,k很小时,不论 n 多大时,前缀数组一定为 3 4 1 2。因此我们可以直接推出前缀固定的排列方式。
在上一步的化简之后,n 总会 < 4*k:
我们根据贪心方法手玩一下四个样例:
8 3
4 5 6 7 8 1 2 3
9 3
4 5 6 7 8 9 1 2 3
10 3
4 5 6 1 8 9 10 2 3 7
11 3
4 5 6 1 2 9 10 11 3 7 8
我们发现 当 n <= 3*k时:
将 [1 2 3 ... n-1 n] 循环左移k次,就是最终答案
当 3*k < n < 4*k 时:
出现了一种特殊的构造方式,我们尝试模拟一下这种构造方式;
实际上是先把最后k个数前移k次,再把前k个数后移k次,如果位置撞了,就顺延到后面的位置上,直到找到空位为止。
剩下的数依次从小到大找到一个空的位置。
时间复杂度O(n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n, k; cin>>n>>k; vector<int> p(n+1);//答案数组 int m = n/(2 * k); //初步判断 if(m == 0) { cout<<"-1\n"; return ; } //化简转移 int j = 1; while(m > 1) { for(int i=j; i<j+k; ++i) { p[i] = i + k; } for(int i=j+k; i<j+2*k; ++i) { p[i] = i - k; } m--; j += 2*k; } //分讨构造 int len = n-j+1; if(len > k*3) { //先放后k个 for(int i=n-k,x=0; i>n-k-k; --i,x++) { p[i] = n-x; } //再放前k个 for(int i=j+k,x=0; x<k and i<=n; i++) { if(!p[i]) { p[i] = j+x; x++; } } //最后总小到大依次放 for(int i=j,x=j+k; i<=n; ++i) { if(!p[i]) { p[i] = x++; } } }else { //直接循环左移k次放 for(int i=j; i<=n-k; ++i) { p[i] = i + k; } for(int i=n-k+1; i<=n; ++i) { p[i] = j + i - (n-k+1); } } for(int i=1; i<=n; ++i) { cout<<p[i]<<" \n"[i==n]; } } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
C:奶龙日历(数学)
题意:
由题意化简得出式子:
(x-y)*(d-1) % w == 0;d, w已经给出,x,y的上界也分别给出。
求满足上式的二元组(x, y)的数量。
思路:
式子化简由题意给出的两个式子相减得到,不再赘述。
由于y > x,设T = y-x,T的范围为[1, min(m, d)-1],得到:
T * (d - 1) % w == 0
设 g = gcd(d-1, w),将w/g,此时d-1 与 (w/g)互质,直接消掉,得到:
T % (w/g) == 0
我们发现T的数量就是(w/g)的所有倍数。
由于我们有了T的范围,所以可以直接用 T/ (w/g) 得到T的数量cnt[T]。
对于每一个T,他的贡献为 min(m, d) - T;
可以发现,随着T的增大,cnt的值是一个等差数列。
因此直接使用等差数列求和公式解决该问题。
最终答案ans = min(m,d) * cnt[T] - (cnt[T] * (cnt[T] -1) / 2 * T);
时间复杂度O(1)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; constexpr i64 M = 1e9; void solve() { i64 m, d, w; cin>>m>>d>>w; i64 k = w / gcd(d-1, w); i64 q = min(m, d) / k; cout<<(min(m, d) * q - (q + 1) * q / 2 * k)<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; cin>>t; while (t--) solve(); return 0; }
D:计计......计算几何???(签到)
题意:
输出三数相乘。
思路:
注意输入有小数,并且输出保留两位
时间复杂度O(1)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { long double x, y, z; cin>>x>>y>>z; cout<<fixed<<setprecision(2)<<(x*y*z)<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1;// cin>>t; while (t--) solve(); return 0; }
E:零域(思维+二分)
题意:
给定一个 n 和 m,求[1 - n]中 有多少个数的阶乘末尾有m个0。
思路:
注意 n 和 m 很大,思维题 or 数位dp or 矩阵,很显然出题组不会考后面两个( )
直接开玩,枚举发现,每当遇到一个5的倍数,就会多一个0。
ok写完了直接交题。
ok喜提 wa
哦,不小心给0也算进去了,给0删了再交一发。
ok喜提 wa
肯定是我枚举的方法不对,重构一遍代码再交一发。
ok喜提 wa
如果你也红温了评论区请扣111
后来我们发现,25的时候会多出两个0,很简单的道理,两个5,在前面激情wa的可能是小丑吧()
回归正题。
所以我们需要预处理5的所有幂数,然后使用二分去查找第一次出现m个0的数,锁定之后,向后查找5个数(为什么是5个数,因为更多的数一定会遇到新的5)。
把这5个数都check一遍,合法的都加进答案里面。
还有一个坑点,奶奶的这题卡常,给我十连评测都卡出来了,ok喜提tle,在我细节优化之后,也是成功通过了。
时间复杂度O(log5(1e18) * log2n * T)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; constexpr i64 N = 1e18; int cnt; i64 p5[1001]; void solve() { i64 n, m; cin>>n>>m; auto check = [&](i64 x) { i64 ans = 0; for(int i=1; i<cnt; ++i) { i64 y = x/p5[i]; if(y == 0) break; ans += y; } return ans; }; i64 l = 0, r = n; while(r-l > 1) { i64 mid = l+r >> 1; if(check(mid) >= m) r = mid; else l = mid; } vector<i64> v; for(i64 i=r; i<=min(n, r+5); ++i) { if(check(i) == m) { v.push_back(i); }else { break; } } if(v.size() == 0) { cout<<"-1\n"; }else { cout<<v.size()<<"\n"; for(auto x: v) { cout<<x<<" "; } cout<<"\n"; } } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); for(i64 i=1; N/i>=5; i*=5) { p5[cnt++] = i; } int t=1; cin>>t; while (t--) solve(); return 0; }
F:神秘三人组(枚举+前缀和)
题意:
给定一个长度为 n 的数组,和一个 p。
找长度为3。公比为p的子序列的个数。
思路:
直接枚举肯定超时。
枚举第一个数或最后一个数不能保证另外两个数的顺序,因此枚举中间的数,保证统计的子序列不重复,并且有序。
所以我们需要时刻保存前缀和后缀。
枚举时,在后缀中查找a[i] * p的数量,在前缀中查找 a[i]/p的数量,如果a[i]%p != 0 直接跳过。
合法时直接 ans += 前缀合法数量 * 1 * 后缀合法数量。
时间复杂度O(nlog2n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n, p; cin>>n>>p; map<i64, i64> mp1, mp2;//mp1为前缀,mp2为后缀 vector<int> a(n+1); for(int i=1; i<=n; ++i) { cin>>a[i]; mp2[a[i]]++; } i64 ans = 0; for(int i=1; i<=n; ++i) {//枚举中间的数 mp2[a[i]]--; if(a[i]%p == 0) { i64 pre = 1ll * a[i] / p; i64 suf = 1ll * a[i] * p; if(mp1.count(pre) and mp2.count(suf)) { ans += mp1[pre] * mp2[suf]; } } mp1[a[i]]++; } cout<<ans<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
G:ICPC上海悲剧时刻(贪心)
题意:
给定一个长度为 n 的数组。
你可以选择任意 k 个数(每个数只能用一次)相乘后放回原数组。
求原数组最大值。
思路:
排序后。
如果最大的 k 个数中有 0 直接数组目前数组最大的数。
否则,输出最大的 k 个数之积。
时间复杂度O(nlog2n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; constexpr int M=998244353; void solve() { int n, k; cin>>n>>k; vector<i64> a(n+1); for(int i=1; i<=n; ++i){ cin>>a[i]; } sort(a.begin()+1, a.end()); i64 ans = 1; for(int i=n; i>n-k; --i) { if(a[i] == 0) { cout<<a[n]%M<<"\n"; return ; } ans = ans * a[i]%M; } cout<<ans<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
H:等等...这是dp对吧(贪心+枚举)
题意:
给定一个长度为 n 的仅包含 'd' 和 'p' 字符串 s。
对于这个字符串的任意子串s[l, r],你可以180°翻转它(相当于先reverse 然后 'p'会变成‘d’, 'd'会变成'p'),得到F(s[l, r])
然后你可以选择任意一个子串,用他的F(s[l, r]) 替换 s[l, r],求最小字典序的串。
当然也可以不操作。
思路:
看见最小字典序直接贪心。
从前到后找第一个不是 d 的位置,然后将该位置固定为左端点 l ,枚举右端点 r ,
依次尝试把F(s[l,r])替换进来,对答案取min。
时间复杂度O(n2)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n; cin>>n; string s; cin>>s; string ans = s; string a1 = s; for(int i=0; i<n; ++i) { if(a1[i] == 'p') { for(int j=i; j<n; ++j) { for(int k=i; k<=j; ++k) { a1[k] = (s[j+i-k]=='p'?'d':'p'); } ans = min(ans, a1); } break; } } cout<<ans<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
I:坐得下吗(思维)
题意:
给定 n 组人,要么是 1个, 要么是两个。
给定一排长度为 L 的座位 (所有人数之和必然等于L)
n组人依次来占座位,在最坏情况下是否能保证每一组人都能连坐在一起。
思路:
思考发现,只有当后面的人是2个的时候可能会做不到一起。
直接把前面的每组都想象的比较邪恶,每组都隔一个位置再坐,这样可以让后面2个人一起的队伍没法插空。
相当于把前面每一组占用的座位都+1
当最后没法隔着坐,并且挤不下时,判断还有没有2人一组的队伍还没坐,如果有就是“No”
否则是"Yes"
需要注意一下,如果最后剩了两个座位,并且刚好是两人一组过来,他们是可以做得下的,只不过他们占用的座位就没办法+1了。
时间复杂度O(n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n, L; cin>>n>>L; for(int i=1; i<=n; ++i) { int x; cin>>x; if(L >= x+1) L -= x+1;//隔着坐能否坐下 else if(L >= x) L -= x;//最后挤一挤能否坐下 else {//只能挤着坐了,且空隙全为1,出现2人一起的就寄 if(x == 2) { cout<<"No\n"; return ; } } } cout<<"Yes\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
J:好长的序列(数学+枚举)
题意:
给定一个长度为 n 的数组和一个 x。
数组会循环延长,数组前缀的第几位能超越x
思路:
开一个ans直接循环加数组会tle。
优化一下枚举方法:
求出一个循环数组的总和 sum,x/sum 得到会完整经过 cnt 次循环数组 。
now = cnt * sum,t = n * cnt
用处理好的次数再去再枚举加上一遍数组的每一位,直到大于x时结束。
时间复杂度O(n)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int n; cin>>n; i64 sum = 0; vector<int> a(n+1); for(int i=1; i<=n; ++i) { cin>>a[i]; sum += a[i]; } i64 x; cin>>x; i64 cnt = x / sum; i64 t = cnt * n; i64 now = cnt * sum; while(now <= x) { now += a[t%n+1]; t++; } cout<<t<<"\n"; } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1;// cin>>t; while (t--) solve(); return 0; }
K:城市链接(并查集)
题意:
给 n 个点, m个操作,
操作一 联通 a b 点(联通是会传递的)
操作二 询问 a b 是否联通
思路:
并查集板子
时间复杂度O(n+m)
AC代码:
#include<bits/stdc++.h> using namespace std; using i64 = long long; int f[10001]; int Find(int x) { if(x == f[x]) return x; return f[x] = Find(f[x]); } void Union(int x, int y) { int fx = Find(x), fy = Find(y); if(fx == fy) return ; f[fx] = fy; } void solve() { int n, m; cin>>n>>m; for(int i=1; i<=n; ++i) f[i] = i; for(int i=1; i<=m; ++i) { int op, a, b; cin>>op>>a>>b; if(op == 1) { T.Union(a, b); }else { if(T.Find(a) == T.Find(b)) { cout<<"Y\n"; }else { cout<<"N\n"; } } } } signed main(void) { ios::sync_with_stdio(false); cin.tie(nullptr); int t=1; //cin>>t; while (t--) solve(); return 0; }
L题黑题(tag3000),不可做题。
完结散花