CSP-S 备战所有题题解(week 1)
或许更慢但更好的阅读体验?
Day1
主要复习:线段树、树状数组(?)、分块、主席树
AT_abc339_e [ABC339E] Smooth Subsequence
一道线段树板子题。首先可以写出下面的 DP 代码:
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(abs(a[i]-a[j])<=d)
dp[i]=max(dp[i],dp[j]+1);
初始化:
由于能转移到
显然中间这一段可以用线段树维护。
核心代码:
for(int i=1;i<=N;i++)
{
int num=dp.query(max(1ll,a[i]-d),min(500000ll,a[i]+d))+1;
dp.update(a[i],a[i],num);
ans=max(num,ans);
}
知识点:线段树,DP
AT_abc339_g [ABC339G] Smaller Sum
分块板板题。
看到这道题,就知道这是一道板板分块了。先分成每块长
-
-
- 散块:暴力!此时时间复杂度 $O(S)$。 - 中间的块:可以考虑先将每一块排序,二分+前缀和出答案。此时时间复杂度 $O\left(\dfrac{n}{S}\times \log S\right)
预处理:要将这
可以发现,其实重头在查询整块,如果令
因为
signed main()
{
int n=read(),tot=0;
int kc=2000;
for(int i=1;i<=n;i++)
{
a[i]=read();
id[i]=(i-1+kc)/kc;
if(id[i]!=id[i-1]) L[i]=i,LLL[++tot]=i;
else L[i]=L[i-1];
}
L[n+1]=n+1;LLL[tot+1]=n+1;
for(int i=1;i<=tot;i++)
{
for(int j=LLL[i];j<LLL[i+1];j++)
w[j]=a[j];
sort(w+LLL[i],w+LLL[i+1]);
for(int j=LLL[i];j<LLL[i+1];j++) sum[j]=sum[j-1]+w[j];
}
int q;
long long lastans=0;
for(cin>>q;q--;)
{
long long l=read(),r=read(),k=read();
l^=lastans,r^=lastans,k^=lastans;
lastans=0;
if(id[l]==id[r])
{
for(int i=l;i<=r;i++) lastans+=(a[i]<=k)*a[i];
write(lastans);puts("");
continue;
}
for(int i=l;id[i]==id[l];i++) lastans+=(a[i]<=k)*a[i];
for(int i=r;id[i]==id[r];i--) lastans+=(a[i]<=k)*a[i];
for(int i=id[l]+1;i<id[r];i++)
{
auto K=upper_bound(w+LLL[i],w+LLL[i+1],k)-w;
lastans+=sum[K-1]-sum[LLL[i]-1];
}
write(lastans);puts("");
}
return 0;
}
知识点:分块(其实也可以主席树)
AT_abc350_f [ABC350F] Transpose
一眼板板题!
先大小写转化、括号匹配,无需多盐。
接着 dfs。显然每次碰到括号就 dfs 一次即可。
void dfs(int l,int r,bool pf)
{
if(pf)
{
for(int i=l;i<=r;i++)
{
if(s[i]=='(')
{
dfs(i+1,dy[i]-1,pf^1);
i=dy[i];
}
else
cout<<s[i];
}
}
else
{
for(int i=r;i>=l;i--)
{
if(s[i]==')')
{
dfs(dy[i]+1,i-1,pf^1);
i=dy[i];
}
else
cout<<s[i];
}
}
}
知识点:搜索,栈。
后记:经过 lihongqian__int128 神犇提醒,我才知道这题正解应该是线段树。对于一次转换,位置
i 会被转换到l+r-i ,其中l,r 是左右括号的坐标。这个操作相当于一个先乘-1 ,再加上l+r 的操作。线段树维护。
AT_abc350_g [ABC350G] Mediator
看起来是一道很难的题,但是暴力过了。
但思考一下会发现也不太好卡。
- 预处理:预处理
f_i ,f_{i,j} 表示i\to j 是否有边。这样的时间复杂度其实是2\times 度数,接近O(n) 。 - 修改:
O(1) 一次。这个操作估计会执行n-1 次。(这样才能让查询效果最大化) - 查询:
O(S_{v_i}+S_{u_i}) 一次。
发现了一件可悲的事情:时间复杂度接近
Day 2 刷题
P5903 【模板】树上 K 级祖先
顾名思义,求树上 K 级祖先的算法叫做树上 K 级祖先。
考虑每一次查询等价于
树上 K 级祖先的时间复杂度:预处理
void logg()
{
Log2[0]=Log2[1]=0;
for(int i=2;i<=500000;i++)
Log2[i]=Log2[i/2]+1;
}
void dfs(int x,int father)
{
depth[x]=depth[father]+1;
for(int i=1;i<=Log2[depth[x]];i++)
fa[x][i]=fa[fa[x][i-1]][i-1];//2^i=2^i-1+2^i-1
for(int i=0;i<G[x].size();i++)
if(G[x][i]!=father) dfs(G[x][i],x);
}
int KZX(int x,int k)
{
int f=0;
while(k)
{
if(k%2) x=fa[x][f];
f++,k/=2;
}
return x;
}
P2396 yyy loves Maths VII
备注:记 lowbit(i) <=> (i&(-i));将
话说那个让 yyy 写程序的那个人怎么能算,怎么没见他去算呢。手捏 24!
虽然
那
如果
方程很显然:对于每一位为
其中
初始化?
void dp(int x)
{
for(int i=x,j;i;i^=j)
j=lowbit(i),(f[x]+=f[x^j])%=mod;
}
Day 2 考试
T1
简述题意:求一个数
不愧是签到题。容易想到如果
T2
简述题意:有两种操作,操作 1 使得
容易想到,最后所有
T3
简述题意:把一个数加上
定义
性质 1 告诉我们,
由于数据随机生成,这种算法几乎卡不掉。
T4
简述题意:定义一条路径的价值为
显然可以先预处理
Day 3
CF915E Physical Education Lessons
动态开点板子题。
如果你需要维护一个
[1,10^9] 的区间,你或许会掏出离散化。但如果它强制在线呢?动态开点! \ \ 动态开点的宗旨是:要用什么点就建立什么节点。以前来说,u 的两个儿子是2u 和2u+1 ,但现在变成了ls,rs 。\ \ 由于每次操作的时间复杂度是O(\log n) ,每次操作就可能创建O(\log n) 级别的点。操作m 次后,也只会创造O(m\log n) 个节点。时间复杂度同样还是O(\log n) 一次。by sLMxf
知识点 get!
CF208E Blood Cousins
首先把这颗树拍平成一个序列。这里的 P 级表祖一定是
那么现在的问题就变成了这样:给定一个序列,求区间
好像没什么好用的数据结构?那就分块启动!
AT_abc180_f [ABC180F] Unbranched
构式数学题。一个一个条件分析。
- 没有自环:好像只要正经点,这条件其实没什么用。
- 点的度数不超过
2 :手玩一下就能发现最后这张图只会有一堆链和环构成。 - 连通块最大个数等于
L :考虑 DP。因为等于L 太难受了,所以考虑统计不超过L ,因为这样就能变成ans(L)-ans(L-1) 了,非常爽!
考虑一下怎么 DP。定义状态:
表示选
问题是转移方程怎么写?由于每个连通块只能是链或环,考虑一下分类讨论。
在分讨之前,我们先讨论链、环的可能数。
- 链:假设这条链长度为
k -
- 环:假设这个环长度为
k(k>1) 。 -
定义:
即链与环的方案数函数。
考虑除了枚举
- 如果这
k 个结点构成的是链。
这k 个节点使用了k-1 条边,其他的i-k 个节点使用了j-k+1 条边,也就是状态dp_{i-k,j-k+1} 。选k 个节点的方案数为剩下的n-i+k-1 个点中选k-1 个点,方案数为\dbinom{n-i+k-1}{k-1} ,贡献为l(k) 。所以总的贡献为:\sum _{k=1}^L dp_{i-k,j-k+1}\times \dbinom{n-i+k-1}{k-1}\times l(k) - 如果这
k 个结点构成的是环。 同链,总贡献为:\sum _{k=1}^L dp_{i-k,j-k}\times \dbinom{n-i+k-1}{k-1}\times h(k)
初始化
然后这题套个逆元就做完了。
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
for(int k=1;k<=min(l,min(i,j+1));k++)
dp[i][j]=(dp[i][j]+dp[i-k][j-k+1]*C(n-i+k-1,k-1)%mod*l(k)%mod)%mod;
for(int k=2;k<=min(l,min(i,j));k++)
dp[i][j]=(dp[i][j]+dp[i-k][j-k]*C(n-i+k-1,k-1)%mod*h(k)%mod)%mod;
}
Day 4 考试
T1 扑克牌
如果能配出
设答案为
1 2 3 4 ... k ... n -- 1
1 2 3 4 ... k ... n -- 2
J 2 3 4 ... k ... n -- 3
J 2 3 4 ... k ... n -- 4
1 J 3 4 ... k ... n -- 5
1 2 J 4 ... k ... n -- 6
. . . . ... . ... n .. .
. . . . ... . ... n .. .
. . . . ... . ... n .. .
1 2 3 4 ... J ... n -- ans
则第
则
T2 T3
石堆没讲
T4 San
这么小的范围,折半搜索即可。
Day 5 考试
CSP-J 模拟,不属于 CSP-S 范围,不讲了。
Day 5 刷题
CF383C Propagating tree
定义
最后统计答案时,乘上自己对权值的贡献即可。
至于操作:拍成 DFS 序,树状数组维护即可。
喵喵喵,没了。
while(m--)
{
int op,x,Val;
cin>>op>>x;
if(op==1)
{
cin>>Val;
add(dfn[x],dep[x]*Val);
add(r[x]+1,-dep[x]*Val);
}
else
{
cout<<dep[x]*sum(dfn[x])+val[x]<<endl;
}
}
Day 6 刷题
CF1439C Greedy Shopping
一道很水的题。
操作 1 水水就过去了。
操作 2 ,我们发现要卡暴力最好的办法是这样的:
但是这样会有一个性质:每一次
考虑线段树二分,这样时间复杂度
int query(int l,int u=1,int L=1,int R=n)
{
if(l<=L&&R<=n)
{
if(a.w[u]<=y)
{
y-=a.query(L,R);
return R-L+1;
}
else if(a.W[u]>y)
{
return 0;
}
}
if(a.lzy[u]) a.pushdown(u,L,R);
int ans=0;
int mid=(L+R)>>1;
if(l<=mid) ans+=query(l,u*2,L,mid);
if(mid+1<=n) ans+=query(l,u*2+1,mid+1,R);
return ans;
}
Week 1 到此结束