历年 CSP / NOIP 补题记录

补题题单


P9753 [CSP-S 2023] 消消乐

AC on 2025.11.17

当年考场上只打出了最无脑的区间dp,导致 135pts 连一等奖都拿不到
所以打暴力应该也要先想优化再打呀,不要有一个很劣的想法就直接去打了
(真的很重要,CSP-S 2025 T3 也是一样的问题呀,最后只拿了 5pts 却花了 1h,假如能先想一下,说不定时间花的更少拿的分却更高,不过可能还是实力没到吧)

暴力优化:\(O(n^2)\) 的想法也很简单,固定左端点再遍历

正解:很显然 \(n^2\) 的算法重复算了哪些要被消掉,所以可以用 \(lst_i\) 记录最远能消除到哪, \(dp_i\) 记录能消几个

但是同种思路不同写法差异也会很大诶,我自己写得要记录一堆东西维护,可是别人理清思路写的很简便

所以想到一种写法后先不要着急打呀,先用样例模拟一遍再想想有什么 corner case 再想想怎么打更简单一点呢

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6+5;
int n, lst[N], dp[N];
ll ans;
string s;
int main(){
	cin >> n;
	cin >> s;
	s = ' ' + s;
	for(int i=2;i<=n;i++){
		for(int j=i-1;j>=1;j=lst[j]-1){
			if(s[i] == s[j]){
				dp[i] = dp[j-1]+1;
				lst[i] = j;
				ans += dp[i];
				break;
			}
		}
	}
	cout << ans;
	return 0;
}

\(\,\)

P1600 [NOIP 2016 提高组] 天天爱跑步

AC on 2025.11.26

也是很久以前就看过的题了,当时看题解都没懂,诶
将一条路径拆分为向上的路径和向下的路径,然后再推式子做树上差分
最重要的其实是统计,进入一个节点后要先记录一下值 \(val\),出节点的时候再减去 \(val\),这样就防止了其他路径对这个节点的影响了
因为考虑贡献,我们只需要这个节点的子树节点,而桶数组还会记录其他节点,所以只需要结束时的值减去开始时的值就好了

#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+5;
int n,m,dep[N],go[N][21],w[N];
int c1[N], c2[N], ans[N];
vector<int> g[N], a1[N], a2[N], b2[N], b1[N];
inline int read(){
	int s=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){s=s*10+c-'0';c=getchar();}
	return s*f;
}
void dfs_pretreat(int x,int fa){
	dep[x]=dep[fa]+1;go[x][0]=fa;
	for(int i=0;i<g[x].size();i++){
		int t=g[x][i];
		if(t==fa)continue;
		dfs_pretreat(t,x);
	}
}
int lca(int x,int y){
	if(dep[x] < dep[y])swap(x, y);
	for(int i=19;i>=0;i--)if(dep[go[x][i]] >= dep[y])x = go[x][i];
	for(int i=19;i>=0;i--)if(go[x][i] != go[y][i])x = go[x][i], y = go[y][i];
	if(x == y)return x;
	return go[x][0];
}
void dfs(int x, int fa){
	int val = c1[w[x]+dep[x]] + c2[w[x]-dep[x]+n];	
	for(int i=0;i<a1[x].size();i++)c1[a1[x][i]]++;
	for(int i=0;i<a2[x].size();i++)c1[a2[x][i]]--;
	for(int i=0;i<b1[x].size();i++)c2[b1[x][i]]++;
	for(int i=0;i<b2[x].size();i++)c2[b2[x][i]]--;
	for(int i=0;i<g[x].size();i++){
		int t=g[x][i];
		if(t == fa)continue;
		dfs(t, x);
	}
	ans[x] = c1[w[x]+dep[x]] + c2[w[x]-dep[x]+n] - val;
}
int main(){
	n=read(),m=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1;i<=n;i++)w[i]=read();
	dfs_pretreat(1,0);
	for(int t=1;t<=19;t++)
		for(int i=1;i<=n;i++)
			go[i][t]=go[go[i][t-1]][t-1];
	for(int i=1;i<=m;i++){
		int s = read(), t = read();
		int LCA = lca(s, t);
		a1[s].push_back(dep[s]);
		a2[go[LCA][0]].push_back(dep[s]);
		b1[t].push_back(dep[s] - 2*dep[LCA] + n);
		b2[LCA].push_back(dep[s] - 2*dep[LCA] + n);
	}
	dfs(1, 0);
	for(int i=1;i<=n;i++)cout<<ans[i]<<' ';
	return 0;
}

\(\,\)

P1514 [NOIP 2010 提高组] 引水入城

AC on 2025.11.28

好妙的题呀
要发现一个性质是:考虑每个顶点,发现它流到底部一定是连续的一个区间,否则某些城市就不可能被经过
(我也不知道该怎么才能注意到这些性质)
然后就比较简单了,注意写之前多考虑点细节,边想边改挺耗时间的

#include<bits/stdc++.h>
using namespace std;
const int N = 505;
int n, m, tot, a[N][N], l[N][N], r[N][N];
bool f[N][N];
struct section{
	int l, r;
	bool operator <(const section &tmp){
		if(l != tmp.l)return l < tmp.l;
		return r > tmp.r;
	}
}s[N];
void dfs(int x, int y){
	if(f[x][y])return;
	f[x][y] = 1;
	if(x == n)l[x][y] = r[x][y] = y;
	if(y!=1 && a[x][y-1] < a[x][y]){
		dfs(x, y-1);
		l[x][y] = min(l[x][y], l[x][y-1]);
		r[x][y] = max(r[x][y], r[x][y-1]); 
	}
	if(y!=m && a[x][y+1] < a[x][y]){
		dfs(x, y+1);
		l[x][y] = min(l[x][y], l[x][y+1]);
		r[x][y] = max(r[x][y], r[x][y+1]); 
	}
	if(x!=n && a[x+1][y] < a[x][y]){
		dfs(x+1, y);
		l[x][y] = min(l[x][y], l[x+1][y]);
		r[x][y] = max(r[x][y], r[x+1][y]); 
	}
	if(x!=1 && a[x-1][y] < a[x][y]){
		dfs(x-1, y);
		l[x][y] = min(l[x][y], l[x-1][y]);
		r[x][y] = max(r[x][y], r[x-1][y]); 
	}
	return;
}
int main(){
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin >> a[i][j];
			l[i][j] = m+1;
		}
	}
	for(int j=1;j<=m;j++){
		dfs(1, j);
		if(r[1][j])s[++tot] = section{l[1][j], r[1][j]};
	}
    int num = 0;
    for(int j=1;j<=m;j++)if(!r[n][j])num++;
    if(num){cout << 0 << endl << num;return 0;}
	sort(s+1, s+tot+1);
	int pos = 1, R = 0, cnt = 0;
	while(pos <= tot){
		if(s[pos].l > R + 1){
			cnt++; 
			R = s[pos].r;
			pos++;
		}
		int maxn = 0;
		while(pos <= tot && s[pos].l <= R + 1){
			maxn = max(maxn, s[pos].r);
			pos++;
		}
		if(maxn > R){
			cnt++;
			R = maxn;
		}
	}
	cout << 1 << endl << cnt;
	return 0;
} 

\(\,\)

posted @ 2025-11-28 13:12  今添  阅读(12)  评论(0)    收藏  举报