历年 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;
}
\(\,\)

浙公网安备 33010602011771号