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;
}