The 19th Zhejiang Provincial Collegiate Programming Contest
比赛总结:
第一次现场省赛,赛前是期望银的,最后三分钟才过了第七题,最后捡个铜。
这场比赛我背大锅,赛前一段时间的状态不太稳定,比赛的时候先是开的第六题用 manacher 去算每个字符为中心的最长的字符串的时候错了,那时候过了一个测试就没想着多测几组,最后浪费了队友一个小时。后来在开的第七题上面先是理解错题目,然后又固执地坚持自己的做法又 debug 不出问题,最后卡了俩小时。
开场四十分钟时候过了四题的时候,在预期之内,第一题题意说实话有点小问题,所以尝试了两个方法,wa 了一次。
接下来就是罚坐了,最短路那一道题写了一个多小时,然后剩下的两道题都在最后半小时极限过掉。
比赛也暴露了很多缺点:一个是代码的实现能力和 debug,最短路实现了一个多小时,大模拟水题 debug 两个小时;接着是知识点的覆盖,manacher 只有一个人懂;最后是配合的问题,互相之间的代码不太喜欢看(可能每个人都不太喜欢看其他人的代码),占着电脑有时候其实思路越来越乱,还不如让其他人重构。
比赛链接:
https://codeforces.com/gym/103687
补题:
A. JB Loves Math
题意:
给定一个两个数 \(a\) 和 \(b\),可以选择一个奇数 \(x\) 和一个偶数 \(y\),选定后无法改变(比赛的时候好像是没有这句话的),每一操作可以让 \(a\) 加上 \(x\) 或者 \(a\) 减去 \(y\),问将 \(a\) 变成 \(b\) 最少需要几步操作。
思路:
每一类讨论一下就行。
当 \(a\) 等于 \(b\) 时,0 步。
当 \(a\) > \(b\) 时,
如果它们之间差偶数,减去一个偶数就行。
如果差奇数,那两步,加上一个奇数,减去一个偶数。
当 \(a\) < \(b\) 时,
如果它们之间差奇数,加上一个奇数就行。
如果差偶数,还要分两类,如果偶数可以分成两个奇数,那么加上两个奇数就行,不然要减去一个偶数后再加上两个奇数。
代码:
#include
using namespace std;
int T = 1, a, b;
void solve(){
cin >> a >> b;
if (a == b) cout << "0\n";
else if (a > b){
if ( (a - b) % 2 == 0 ) cout << "1\n";
else cout << "2\n";
}
else {
if ( (b - a) & 1 ) cout << "1\n";
else {
int t = (b - a) / 2;
if (t & 1) cout << "2\n";
else cout << "3\n";
}
}
}
int main() {
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while(T--)
solve();
return 0;
}
B. JB Loves Comma
题意:
给定一个字符串,在每一个 "cjb" 子串后面加入一个逗号。
思路:
按题意计算。
代码:
#include
using namespace std;
string s;
int main(){
cin >> s;
for (int i = 0; i < s.size() - 2; i ++ ){
if (s.substr(i, 3) == "cjb"){
s.insert(s.begin() + i + 3, ',');
}
}
cout << s << "\n";
return 0;
}
C. JB Wants to Earn Big Money
题意:
\(n\) 个人想要买股份,\(m\) 个人想要出售股份,给出每个人想买的股份的价格和想出售的股份的价格,最后的定价为 \(x\)。
当想买的人的价格大于等于 \(x\) 时,它会加入交易,当想出售的人的价格小于等于 \(x\) 时,它会加入交易,问最后有多少个人加入交易。
思想:
按照题意计算就行。
代码:
#include
using namespace std;
int n, m, x, a, b, ans;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n >> m >> x;
for (int i = 1; i <= n; i ++ ){
cin >> a;
if (a >= x)
ans ++ ;
}
for (int i = 1; i <= m; i ++ ){
cin >> b;
if (b <= x)
ans ++ ;
}
cout << ans << "\n";
return 0;
}
G. Easy Glide
题意:
从点 \(s\) 出发到点 \(t\),以 v1 的速度开始运动,中间有 \(n\) 个点加速点,到达加速点之后的 3 秒会以 \(v2\) 的速度运动,然后又变回 \(v1\),问从 \(s\) 到 \(t\) 的最短时间是多少。
思路:
显然,从起点出发后,肯定去终点或者去加速点,所以构建起点到所有终点和加速点的边。
在某个加速点,会去另一个加速点或者去终点,所以构建每个加速点到其它加速点和终点的边。
然后通过最短路算法计算就行。
代码:
#include
using namespace std;
#define PDI pair
#define fi first
#define se second
const int N = 1e3 + 10, M = 2e6 + 10;
int n, v[N];
double x[N], y[N], v1, v2, dis[N];
vector < PDI > g[M];
double calT1(int a, int b){
double dx = x[a] - x[b], dy = y[a] - y[b];
double d = sqrt(dx * dx + dy * dy);
return d / v1;
}
double calT2(int a, int b){
double dx = x[a] - x[b], dy = y[a] - y[b];
double d = sqrt( dx * dx + dy * dy );
if (d <= 3 * v2) return d / v2;
else return (d - v2 * 3) / v1 + 3;
}
void dijkstra(){
for (int i = 1; i <= n + 1; i ++ )
dis[i] = 1e7;
dis[0] = 0;
priority_queue< PDI, vector, greater > q;
q.push( {0, 0} );
while ( q.size() ){
PDI t = q.top();
q.pop();
int x = t.se;
if (v[x]) continue;
v[x] = 1;
for (auto tt : g[x]){
double d = tt.fi;
int y = tt.se;
if (dis[y] > dis[x] + d){
dis[y] = dis[x] + d;
q.push( { dis[y], y} );
}
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i ++ )
cin >> x[i] >> y[i];
cin >> x[0] >> y[0] >> x[n + 1] >> y[n + 1];
cin >> v1 >> v2;
for (int i = 1; i <= n + 1; i ++ )
g[0].push_back({calT1(0, i), i});
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n + 1; j ++ )
g[i].push_back({calT2(i, j), j});
dijkstra();
printf("%.12lf\n", dis[n + 1]);
return 0;
}
I. Barbecue
题意:
长为 \(n\) 的字符串,\(q\) 次询问,每次选择一段区间 \(l\) 到 \(r\),\(Putata\) 和 \(Budada\) 轮流在字符串上博弈,每次可以删除选中字符串的开头字符或者结尾字符,当有人拿到的是回文的字符时,就输了。每次询问判断谁获胜。
思路:
首先判断初始的字符串是不是回文的,通过 \(manacher\) 或者 \(hash\) 判断,是的话 \(Putata\) 直接就输了。
如果不是,因为采取最优的策略,肯定不会让自己得到回文字符串,所以最后只可能是剩下一个字符的时候。判断一下选中字符串的奇偶性即可。
代码:
#include
using namespace std;
const int N = 2e6 + 10;
char s[N << 1];
int p[N << 1], n, q;
string ss;
void manacher(){
s[0] = '-', s[1] = '#';
for (int i = 0; i < n; i ++ ){
s[2 * i + 2] = ss[i];
s[2 * i + 3] = '#';
}
n = n * 2 + 1;
s[n + 1] = '+';
int mid = 0, r = 0;
for (int i = 1; i < n; i ++ ){
if (i < r) p[i] = min(p[(mid << 1) - i], r - i);
else p[i] = 1;
while(s[i - p[i]] == s[i + p[i]]) p[i]++;
if (i + p[i] > r){
r = i + p[i];
mid = i;
}
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> q;
cin >> ss;
manacher();
for (int i = 1; i <= q; ++ i) {
int x, y;
cin >> x >> y;
int xx = x, yy = y;
x *= 2, y *= 2;
int mid = (x + y) / 2;
if ( 2 * p[mid] - 1 >= y - x + 1 ) cout << "Budada\n";
else {
if ( (yy - xx + 1) % 2 == 0 ) cout << "Budada\n";
else cout << "Putata\n";
}
}
return 0;
}
L. Candy Machine
题意:
给定 \(n\) 个数,选择一个子集,问让子集中严格大于子集平均数的数最多有多少。
思路:
贪心的思想,为了让平均值最小,那么一定选中了最小的几个,先排个序。答案一定是某个前缀的值,通过双指针或者二分求解。
代码:
#include
using namespace std;
#define LL long long
LL n, s, ans;
int main() {
ios::sync_with_stdio(false);cin.tie(0);
cin >> n;
vector a(n + 1);
for (int i = 1; i <= n; i ++ )
cin >> a[i];
sort(a.begin() + 1, a.end());
for (LL l = 1, r = 1; r <= n; r ++ ){
s += a[r];
while (l < r && a[l] * r <= s) l ++ ;
ans = max(ans, r - l + 1);
}
cout << ans << "\n";
return 0;
}
M. BpbBppbpBB
题意:
将 \(C\) 和 \(S\) 两种类型的图形不重叠地放到一个矩阵中。
已知放好之后的矩阵,求出每个图形放了多少个,图形可能旋转了 90 度的倍数之后放置进去的。
思路:
因为没有重叠,可以先找到中间的白块,然后判断一下这个白块是 \(C\) 的还是 \(S\) 的,直接模拟求解即可。
比赛的时候 \(if\) 忘了 \(else\),卡了俩小时,虽然 a 了,但是,以后不想模拟了。
因为每个图形黑方块的数量和中间白色块的数量是固定的,所以考虑推数学公式。
设有 \(x\) 个 \(C\),\(y\) 个 \(S\),记 \(sumb\) 为黑方块的数量,\(cntw\) 为中间白色块的数量。
得到方程:
\(146 * x + 100 * y = sumb\)
\(2 * x + y = cntw\)
推出 \(x = \frac{100 * cntw - sumb}{54}, y = cntw - 2 * x\)。
在判断中间白块的时候,起码要判断 6 * 6 的,4 * 4 的会被卡。因为四个角的黑块都属于一个图形的时候,这个位置的白块其实不需要被记录。
代码:
#include
using namespace std;
const int N = 1e3 + 10;
int n, m, cntw, sumb;
char a[N][N];
vector t = {
"######",
"##..##",
"#....#",
"#....#",
"##..##",
"######"
};
int check(int x, int y){
for (int i = 0; i <= 5; i ++ )
for (int j = 0; j <= 5; j ++ )
if (x < 1 || y < 1 || x + i > n || y + j > m || a[x + i][y + j] != t[i][j])
return 0;
return 1;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> a[i][j];
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (a[i][j] == '#') sumb ++ ;
else cntw += check(i - 1, j - 2);
int x = (cntw * 100 - sumb) / 54;
cout << x << " " << cntw - 2 * x << "\n";
return 0;
}