CSPS2024题目总结

T1 决斗

签到题,考场上10min就做出来了。

我的方法是排序之后贪心打怪,就是用尽量小的怪去打现在场上最小的怪。用一个同侧双指针实现。 \(O(nlogn)\)

另一种方法注意到了值域很小,可以放进桶里做到 \(O(n)\)

还有一种方法。junjun大佬说答案就是出现次数最多的数的出现次数,感性理解一下也是对的。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
int a[N], n;
signed main(){
// 	freopen("duel.in","r",stdin);
// 	freopen("duel.out","w",stdout);
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n;
	F(i, 1, n) cin >> a[i];
	sort(a + 1, a + n + 1);
	int nw = 1, ans = n;
	F(i, 2, n){
		if(a[i] > a[nw]) -- ans, ++ nw;
	}
	cout << ans << '\n';
	return 0;
}

T2 超速检测

第一问:算出每辆车超速的临界位置 \(x\),看一下超速区间里面有没有检测仪,有的话答案 +1。

如果 \(a_i \lt 0\),那么超速区间为 \([d[i], x]\)

如果 \(a_i \ge0\),那么超速区间为 \([x, p[m]]\)

这里涉及到非常多令人作呕的上下取整问题,下来调了一个多小时……

第二问:把第一问里面有效的超速区间拿出来,问题模型就是:

选最少的特殊点,使得每个区间都包含至少一个特殊点。

考场上想到了二分+贪心,但最终坚定地选择了线段树(嘻嘻嘻……还没写出来,我是sb)

考场做法疑似 \(60 \sim 80pts\),哎我都不奢求什么了,希望 \(60\) 分保底顺利拿到……(考场上真是紧张坏了)

正解就是先给区间排序(以 \(l\) 为第一关键字,\(r\) 为第二关键字)然后贪心,由于 \(l\) 已经保证递增了,所以贪心的功夫全在 \(r\) 怎么处理上。

\(pmin\) 表示最新选择的点。发现一个性质:\(pmin\) 一定是尽量靠右的一个位置,因为这样的话,随着 \(l\) 增加, \(pmin\) 能被尽量多的区间包含。

只要 \(l_i \le pmin\)\(pmin\) 就可以一直产生贡献,并且如果 \(r_i \lt pmin\),为了实际上真的把 \(i\) 区间装进来了,要将 \(pmin\) 更新为 \(r_i\)

\(l_i \gt pmin\),我们就新开一个点,初始位置 \(pmin = r_i\)

画两个图玩一下,发现很对。(嘻嘻……

思路就是这样。

自己感觉难点一方面是要往简单想,既然一开始有贪心的想法,为什么不先试着多玩两个图甚至码一下呢?

另一方面就是第一问细节是真多,要套公式,同时还要注意上下取整的问题。(如果写 \(nlogn\) 好像码起来会稍微容易一点)

值域较小,可以做到 \(O(Tn)\)。(其实我还是排了个序)

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
char buf[100], *p1 = buf, *p2 = buf;
inline int gc(){
	return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100,stdin),p1==p2)?EOF:*p1++;
}
inline int rd(){
	int x = 0; char ch; bool f = 1;
	while(!isdigit(ch = gc())) f ^= (ch == '-');
	do x = (x << 3) + (x << 1) + (ch ^ 48); while(isdigit(ch = gc()));
	return f ? x : -x;
}
const int N = 1e5 + 5;
struct cat{
	int l, r;
	bool operator < (const cat &other)const{
		return l == other.l ? r < other.r : l < other.l;
	}
}e[N];
int T, n, m, L, o, cnt = 0;
int d[N], v[N], a[N], p[N];
int pos[1000006], s[1000006];
void init(){
	n = rd(), m = rd(), L = rd(), v[0] = rd();
	F(i, 1, n) d[i] = rd(), v[i] = rd(), a[i] = rd();
	F(i, 1, m) p[i] = rd(), pos[p[i]] = i, s[p[i]] ++ ;
}
int tot = 0;
int solve1(){
	int st = 1, ans1 = 0;
	F(i, 1, L){
		if(pos[i]){
			F(j, st, i) pos[j] = pos[i];
			st = i + 1;
		}
		s[i] += s[i - 1];
	}
	G(i, L, 1){
		if(pos[i]) break;
		pos[i] = m + 1;
	}
	F(i, 1, n){
		ll x;
		if(a[i] == 0){
			if(v[i] <= v[0]) continue;
			if(s[L] - s[d[i] - 1]){
				e[++ cnt] = (cat){pos[d[i]], m};
				++ ans1;
			}
			continue;
		}
		x = (1ll * v[0] * v[0] - 1ll * v[i] * v[i]) / (2ll * a[i]) + d[i];
		if(a[i] > 0){
			if(v[0] < v[i]){
				if(s[L] - s[d[i] - 1]){
					e[++ cnt] = (cat){pos[d[i]], m};
					++ ans1;
				}
			}
			else{
				++ x; // begin to over
				if(x > L) continue;
				if(s[L] - s[x - 1]){
					e[++ cnt] = (cat){pos[x], m};
					++ ans1;
				}
			}	
		}
		else{
			if(v[i] <= v[0]) continue;
			if((1ll * v[0] * v[0] - 1ll * v[i] * v[i]) % (2ll * a[i]) == 0) -- x; // end to over
			if(x < d[i]) continue;
			int l = pos[d[i]], r = pos[x + 1] - 1;
			if(x >= L) x = L, r = m;
			if(s[x] - s[d[i] - 1]){
				e[++ cnt] = (cat){l, r};
				++ ans1;
			}
		}
	}	
	return ans1;
}
int solve2(){
	sort(e + 1, e + cnt + 1);
	int pmin = 0, ans2 = 0;
	F(i, 1, cnt){
		int l = e[i].l, r = e[i].r;
		if(l > pmin){
			++ ans2;
			pmin = r;
		}
		else pmin = min(pmin, r);
	}
	return m - ans2;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	T = rd();
	while(T --){
		init();
		int ret1 = solve1(), ret2 = solve2();
		printf("%d %d\n", ret1, ret2);
		F(i, 0, L) pos[i] = s[i] = 0;
		cnt = 0;
	}
	return 0;
}

T3 染色

\(O(n^2)\)

先考虑 \(O(n^2)\) 做法,记 \(f[i][1/0]\) 表示 \(i\) 的颜色和 \(i - 1\) 一样 / 不一样 的最大得分。

对于颜色一样,有:

\[f[i][1] = max(f[i - 1][1], f[i - 1][0]) + a[i]/0 \]

对于颜色不一样,我们需要在 \(1 \sim i - 1\) 中找一个位置 \(j\)\(i\) 染成同种颜色,并且它们之间全是异色,形如:

\[1000\dots0001 \]

\(j\) \(i\)

思考一下,会发现现在最大的问题是 \(j + 1\) 位置会和哪个位置匹配。转移可大致写为:

\[f[i][0] = \max_{1 \le j \le i - 2}max(f[j][0], f[j][1]) + val(j + 2, i - 1) + ? \]

问号就是 \(j + 1\) 的贡献。难道为了这个地方我们又要多枚举一重循环?

这里有一个很逆天的 \(trick\),我们可以从 \(f[j + 1][0]\) 转移过来!

解释一下:回到定义,我们只规定颜色一不一样,没说到底谁0谁1,所以 \(f[j + 1][0]\) 就不用再考虑 \(j + 1\) 的贡献了,前面已经求出来了。

新的转移为:

\[f[i][0] = \max_{1 \le j \le i - 2} f[j + 1] + val(j + 2, i - 1) + a[i]/0 \]

\(val\) 可以前缀和预处理,于是就有了一个 \(O(n^2)\) 做法。

拆式子

加上前缀和后,式子可以写成:

\[f[i][0] = s[i - 1] + a[i] / 0 + \max_{1 \le j \le i - 2} f[j + 1] - s[j + 1] \]

对于 \(\max\) 里面可以直接前缀最大值,记作 \(maxn\)

对于 \(a[i]/0\),记 \(mx[a[i]]\) 表示

\[\max_{1 \le j \le i - 1 \cap a[j] = a[i]}f[j] - s[j] + a[i] \]

本质是一种桶的思想。

所以 \(f[i][0] = \max(maxn, mx[a[i]] + s[i - 1])\)

时间复杂度 \(O(Tn)\)

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using std::max;
using ll = long long;
char buf[100], *p1 = buf, *p2 = buf;
inline int gc(){ return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100,stdin),p1==p2)?EOF:*p1++; }
inline int rd(){
	int x = 0; char ch;
	while(!isdigit(ch = gc()));
	do x = (x << 3) + (x << 1) + (ch ^ 48); while(isdigit(ch = gc()));
	return x;
}
const int N = 2e5 + 5;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll f[N][2], s[N], mx[1000006];
int n, T, a[N];
void init(){
	n = rd(); int amax = 0;
	F(i, 1, n) a[i] = rd(), amax = max(amax, a[i]);
	F(i, 0, n) f[i][0] = f[i][1] = 0;
	F(i, 0, amax) mx[i] = -inf;
	s[0] = s[1] = 0; F(i, 2, n) s[i] = s[i - 1] + ((a[i] == a[i - 1]) ? a[i] : 0);
}
ll solve(){
	ll maxn = 0;
	F(i, 1, n){
		f[i][1] = max(f[i - 1][1], f[i - 1][0]) + ((a[i] == a[i - 1]) ? a[i] : 0);
		f[i][0] = s[i - 1] + maxn;
		if(mx[a[i]] != -inf) f[i][0] = max(f[i][0], s[i - 1] + mx[a[i]]);
		mx[a[i - 1]] = max(mx[a[i - 1]], f[i][0] - s[i] + a[i - 1]);
		maxn = max(maxn, f[i][0] - s[i]);
	} return max(f[n][0], f[n][1]);
}
signed main(){
	T = rd(); while(T --) init(), printf("%lld\n", solve());
	return fflush(0), fclose(stdin), fclose(stdout), 0;
}

总结

这次比赛让我意识到了 “策略” 和 “思维变现” 的重要性。

关于策略,错误地沿用了一贯打模拟赛时的策略,在 T1 切掉后没有选择先把题都读一遍然后找好拿的分先拿,而是选择硬刚 T2,打到 16:30 还是一分未拿直接慌了,4h 赛制和 4.5h 的实际感受完全不一样!

如果让我再决策一次,我会先通读题目,然后把 T2 60pts,T3 20 ~ 35pts 先写了(不是写完就至少180谁不心动?)然后我可能会开 T2 正解,也可能开 T3 50分,不好还原当时的心境……但这个时候再写 T2 也会淡定许多吧?T4那个暴力嘛,看最后时间够不够了。

关于思维变现,昨晚已和 lyy 深入交流过了(别人是物竞啊%%%)这次 T2 想过二分+贪心然后否了选择了线段树,写线段树又一直再自我怀疑;T3 一眼 dp 但慌乱之中没有心情也没有时间深入想下去。

我觉得有两点反思。

第一,肯定自己,思维肯定不差!这两天 “采访了” 机房很多同学,他们想到了的点,我也都想到了类似的。“伸脖子是一刀,缩脖子也是一刀”,要自信!

第二,矫正自己,现在最大的问题是 思维很活跃 但 变现到成绩上很少!要刷真题,刷好题,不断地去摸索什么难度的题一般 不会想什么,应该先想什么,哪些点子一定是不该出现在这个位置的题……只要你找到那条思维的主线,你的noip一定可以1=!

“新旧之间没有怨讼,唯有真与伪是大敌。”——柴静

posted @ 2024-10-28 21:33  superl61  阅读(131)  评论(0)    收藏  举报