20250329模拟赛
20250329模拟赛
T3.string
题面:
给定一个长为 \(n\) 字符串 \(s\),\(Q\) 次操作,每次修改一个位置的字符或询问子串 \(s[l:r]\) 有多少本质不同的子序列。字符集只有 \(\{A,B,C\}\),\(n,Q\leq 10^5\)
题解:
考虑朴素 \(dp\),设 \(f_i\) 表示以 \(i\) 结尾的不同子序列方案数。设 \(x,y,z\) 是 \(i\) 前面(不含 \(i\))第一个 \(A,B,C\) 的位置,有 \(f_i=1+f_x+f_y+f_z\)。
\(f_x,f_y,f_z\) 中的方案结尾字符不同显然互不相同。
其余的方案,例如 \(f_k\) (\(s_k=A\))中的方案一定会在 \(f_x\) 中找到一样的方案。
还需要支持单点修改,用 \(DDP\) 解决,维护矩阵 \([f_x,f_y,f_z,1]\) 转移。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=1e5+5,mod=998244353;
int n,m;
char s[N];
struct matrix{
int n,m;
ll v[4][4];
ll* operator [](int i){ return v[i]; }
}t[N<<2],S;
ll Mod(ll x){
while(x>=mod) x-=mod;
return x;
}
matrix upd(int v){
matrix S;
S.n=S.m=4;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++) S[i][j]=0;
for(int i=0;i<4;i++) S[i][i]=1;
for(int i=0;i<4;i++) S[i][v]=1;
return S;
}
matrix operator * (matrix a,matrix b){
matrix c;
c.n=a.n;c.m=b.m;
for(int i=0;i<c.n;i++)
for(int j=0;j<c.m;j++) c[i][j]=0;
for(int i=0;i<a.n;i++)
for(int k=0;k<a.m;k++)
for(int j=0;j<b.m;j++)
(c[i][j]+=a[i][k]*b[k][j])%=mod;
return c;
}
void build(int s,int l,int r){
if(l==r){
t[s]=upd(::s[l]-'A');
return ;
}
int mid=l+r>>1;
build(s<<1,l,mid);
build(s<<1|1,mid+1,r);
t[s]=t[s<<1]*t[s<<1|1];
}
void update(int s,int l,int r,int x,int v){
if(l==r) return t[s]=upd(v),void();
int mid=l+r>>1;
if(x<=mid) update(s<<1,l,mid,x,v);
else update(s<<1|1,mid+1,r,x,v);
t[s]=t[s<<1]*t[s<<1|1];
}
matrix query(int s,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[s];
int mid=l+r>>1;
if(R<=mid) return query(s<<1,l,mid,L,R);
if(L>mid) return query(s<<1|1,mid+1,r,L,R);
return query(s<<1,l,mid,L,R)*query(s<<1|1,mid+1,r,L,R);
}
int main(){
// freopen("string.in","r",stdin);
// freopen("string.out","w",stdout);
n=read();m=read();
scanf("%s",s+1);
S.n=1;S.m=4;
S[0][0]=S[0][1]=S[0][2]=0;S[0][3]=1;
build(1,1,n);
while(m--){
int op=read();
if(op==1){
char ch[2];
int x=read();
scanf("%s",ch);
update(1,1,n,x,ch[0]-'A');
}
else{
int l=read(),r=read();
matrix ans=S*query(1,1,n,l,r);
printf("%lld\n",Mod(ans[0][0]+ans[0][1]+ans[0][2]));
}
}
return 0;
}
T4.road
题面:
给定一个长为 \(n\) 的数组 \(a_i\),每次可以选一个区间 \([l,r]\) 满足 \(\forall i\in[l,r],a_i>0\) 将其中所有 \(a_i\) 减一,花费 \((r-l+1)^2\) 的代价。求操作次数最小值和在此前提下的花费最小值和最大值。
题解:
达到操作次数的最小值显然有策略每次取最长的满足条件的区间操作。
考虑总操作长度是固定为 \(\sum a_i\) 的。对于两个操作长度 \(x,y(x\leq y)\),都有 \((x-1)^2+(y+1)^2>x^2+y^2\)。所以对于最大值,尽可能将操作长度向两边延伸,最小值反之。
所以有最大值策略和最小次数策略相同;最小值策略仅需保证操作长度最大值最小。
最大值是好求的。考虑最小值怎么求。一次操作是区间减一,转成差分即选两个位置加一减一,目标是将正负数配对使得最后全是 \(0\),最大长度最小。这是一个经典的贪心问题,每次取最前方的正数和负数配对即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=3e5+5,mod=1e9+7;
int a[N],n;
namespace subtask1{
int st[N],top,l[N],r[N],siz[N],rt;
ll tim,mx=0;
void dfs(int x){
siz[x]=1;
if(l[x]){
dfs(l[x]);
siz[x]+=siz[l[x]];
}
if(r[x]){
dfs(r[x]);
siz[x]+=siz[r[x]];
}
}
void dp(int x,int w){
int v=a[x]-w;
tim+=a[x]-w;
mx+=1ll*v*siz[x]%mod*siz[x]%mod;
if(mx>=mod) mx-=mod;
w=a[x];
if(l[x]) dp(l[x],w);
if(r[x]) dp(r[x],w);
}
void solve(){
for(int i=1;i<=n;i++){
while(top&&a[i]<a[st[top]]){
l[i]=st[top];
top--;
}
if(top) r[st[top]]=i;
st[++top]=i;
}
rt=st[1]; dfs(rt); dp(rt,0);
printf("%lld\n%lld\n",tim,mx);
}
}
namespace subtask2{
ll mn;
void solve(){
for(int i=n+1;i>=1;i--) a[i]-=a[i-1];
int l=1,r=1;
while(l<=n+1&&r<=n+1){
while(l<=n+1&&a[l]<=0) l++;
while(r<=n+1&&a[r]>=0) r++;
int v=min(abs(a[l]),abs(a[r]));
(mn+=1ll*v*(r-l)%mod*(r-l)%mod)%=mod;
a[l]-=v;a[r]+=v;
}
printf("%lld",mn);
}
}
int main(){
// freopen("road.in","r",stdin);
// freopen("road.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i]=read();
subtask1::solve();
subtask2::solve();
return 0;
}