2022.10.12 CSP2022 模拟赛二
因为去某神秘学校集训的题补不到,导致那边的题没有补,所以大概就以昨天的题为一了。
之后的模拟赛大概都会补吧,如果实在不补了就空着算了。
为什么这个是二,一是我出的。
括号序列
Source: CF1153C *1700。
哈哈 *1700 写挂了,不愧是我。
符合条件的序列无非是首括号和末括号匹配,然后中间是一个合法的括号序列。
所以说问题就是构造括号序列,实际上只需要将中间的括号序列从左往右先放左括号放完了再放右括号就行了。
做到线性。
Code
const int N=3e5+5;
char ch[N],ans[N];
int n,st[N],top;
void no() {puts(":(");exit(0);}
void solve(int L,int R) {
int a=n/2-1,b=n/2-1;
FOR(i,L,R) {
if(ch[i]=='(') a--;
if(ch[i]==')') b--;
}
FOR(i,L,R) {
if(ch[i]=='(') ans[i]='(';
if(ch[i]==')') ans[i]=')';
if(ch[i]=='?') {
if(a) ans[i]='(',a--;
else if(b) ans[i]=')',b--;
}
}
FOR(i,L,R) {
if(ans[i]=='(') st[++top]=i;
else {
if(top==0) no();
top--;
}
}
if(top) no();
}
int main() {
n=read();
scanf("%s",ch+1);
if(n%2==1) no();
if(ch[1]==')'||ch[n]=='(') no();
ans[1]='(',ans[n]=')';
solve(2,n-1);
FOR(i,1,n) printf("%c",ans[i]);
}
数列选数
Source: CF1616D *2000。
算法一:
给每个数减掉 \(x\),然后问题就变成了区间和 \(\ge 0\)。
可以证明需要检查的区间只有长度为 \(2\) 和 \(3\) 的,检查后打标记即可。
时间复杂度线性。
Code
const int N=5e4+5,inf=2e7;
int n,a[N];
void solve() {
n=read();
FOR(i,1,n) a[i]=read();
int x=read(),ans=0;
FOR(i,1,n) a[i]-=x;
FOR(i,2,n) {
if(i>=3&&a[i]+a[i-1]+a[i-2]<0) a[i]=inf,ans++;
else if(a[i]+a[i-1]<0) a[i]=inf,ans++;
}
printf("%d\n",n-ans);
}
int main() {
int T=read();
while(T--) solve();
}
做法二:
化简式子得到 \(s_r-s_{l-1}\ge rx-(l-1)x\),对于每个 \(r\),我们考虑找出每一个区间。
现在问题变成给定若干个区间,需要染点使得每个区间都至少有一个点,这个可以贪心。
记上一个不取的位置是 \(las\),我们希望检查 \(\forall l\in (las,r),s_r-rx\ge s_l-(l-1)x\),这说明这个点可不可以取。
这是一个二维数点,随便搞搞可以做到线性对数,可以扩展到 $\le $ 的情况。
Code
const int N=5e4+5;
int n,a[N],x;
ll s[N],d[N];
int bit[N];
void add(int x,int v) {for(;x<=n+1;x+=x&(-x)) bit[x]+=v;}
int ask(int x) {int ret=0;for(;x;x-=(x&-x)) ret+=bit[x];return ret;}
void solve() {
n=read();
memset(s,0,sizeof s),memset(d,0,sizeof d),memset(bit,0,sizeof bit);
FOR(i,1,n) a[i]=read(),s[i]=s[i-1]+a[i];
x=read();
FOR(i,1,n) s[i]-=1ll*i*x,d[i]=s[i];
sort(d+1,d+n+2);
FOR(i,0,n) s[i]=lower_bound(d+1,d+n+2,s[i])-d;
int las=0,ans=0,sum=0;
FOR(i,1,n) {
if(ask(s[i])!=sum) {
FOR(j,las,i-1) sum--,add(s[j],-1);
ans++,las=i;
}
sum++,add(s[i-1],1);
}
printf("%d\n",n-ans);
}
int main() {
int T=read();
while(T--) solve();
}
单向道路
Source: 「CEOI2017」One-Way Streets。
考虑缩边双,边双内的条件可以忽略不计,缩完边双之后图变成了一个树。
树上的问题,考虑某一个条件 \(u,v\),实际上可以使用树上差分,每次对 \(c_x+1\),\(c_y-1\),再对根 DFS 就能得到某个点到其父亲的边的方向。
可以精细实现到线性但没必要。
Code
const int N=1e5+5;
vector<pii> G[N],T[N];
int n,m,dfn[N],low[N],dfc,bel[N],cc;
int st[N],top;
void tarjan(int u,int id) {
st[++top]=u;
dfn[u]=low[u]=++dfc;
for(pii ed:G[u]) {
int v=ed.fi,e=ed.se;
if(e+id==0) continue;
if(!dfn[v]) tarjan(v,e),chkmin(low[u],low[v]);
else chkmin(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
cc++;
while(top) {
int v;
bel[v=st[top]]=cc,top--;
if(u==v) break;
}
}
}
int c[N];
int vis[N],f[N],u[N],v[N];
int ans[N];
void dfs2(int u,int fa,int id,int e) {
vis[u]=1;
for(pii ed:T[u]) if(ed.fi!=fa) f[ed.fi]=ed.se,dfs2(ed.fi,u,abs(ed.se),ed.se),c[u]+=c[ed.fi];
if(c[u]) ans[id]=(1ll*c[u]*e<0?1:2);
}
int main() {
n=read(),m=read();
FOR(i,1,m) {
int x=read(),y=read();
G[x].pb({y,i}),G[y].pb({x,-i});
u[i]=x,v[i]=y;
}
FOR(i,1,n) if(!dfn[i]) tarjan(i,0);
FOR(i,1,n) for(pii ed:G[i]) if(bel[i]!=bel[ed.fi]) T[bel[i]].pb({bel[ed.fi],ed.se});
int p=read();
FOR(i,1,p) {
int x=bel[read()],y=bel[read()];
c[x]++,c[y]--;
}
FOR(i,1,cc) if(!vis[i]) dfs2(i,0,0,0);
FOR(i,1,m) printf("%c",ans[i]==0?'B':(ans[i]==1?'R':'L'));
puts("");
}
概率期望
Source: CF1153F *2600 但是数据范围加强到 \(10^7\)。
首先答案可以最后再乘上 \(l\),下面就讨论 \(l=1\) 的情况。
推式子:
枚举某一个位置 \(x\) 被覆盖了 \(k\) 次,求概率,有如下式子:
注意到 \(x\) 可以是实数,这就很困难,但是我们只需要求总体被覆盖 \(k\) 次的答案,积分得:
对后面的 \((1-2x(1-x))^{n-i}\) 施以二项式定理:
后面的积分此时是一个 Beta 积分,可以看 Ref 进行了解。
总之,我们只摆事实:\(B(i,j)=\int^1_0 x^{i}(1-x)^{j}dx=\frac{(i-1)!(j-1)!}{(i+j-1)!}\),可以将式子改写为:
暴力计算这个式子可以做到 \(O(n^2)\)。
注意到后面的式子出现大量 \(i+j\) 并且某个组合恒等式 \(\binom{n}{i}\binom{n-i}{j}=\binom{n}{i+j}\binom{i}{j}\),我们转而枚举 \(i+j\):
后面的 \(\sum_{j=0}^i (-1)^j\binom{i}{j}\) 可以使用组合恒等式 \(\binom{i}{j}=\binom{i}{i-j}\) 化为卷积形式,使用 NTT 可以做到 \(O(n\log n)\)。
但是这不够强,联系组合恒等式 \(\binom{i}{j}=\binom{i-1}{j}+\binom{i-1}{j-1}\),我们可以得到 \(\sum_{j=0}^i (-1)^j\binom{i}{j}=(-1)^{i-k}\binom{i-1}{i-k}\),则我们的答案为:
做就完了,时间复杂度 \(O(n)\)。
Code
const int N=10005,mod=998244353;
int n,k,l;
int fac[N],inv[N],p2[N],p1[N];
int C(int n,int m) {return 1ll*fac[n]*inv[n-m]%mod*inv[m]%mod;}
int B(int i,int j) {return 1ll*fac[i]*fac[j]%mod*inv[i+j+1]%mod;}
int fpow(int x,int y) {
int ret=1;
for(;y;y>>=1) {
if(y&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;
}
return ret;
}
int main() {
n=read(),k=read(),l=read();
fac[0]=1,p2[0]=1,p1[0]=1;
FOR(i,1,2*n+1) fac[i]=1ll*fac[i-1]*i%mod,p2[i]=2ll*p2[i-1]%mod,p1[i]=mod-p1[i-1];
inv[2*n+1]=fpow(fac[2*n+1],mod-2);
ROF(i,2*n,0) inv[i]=1ll*inv[i+1]*(i+1)%mod;
int ret=0;
FOR(i,k,n) {
int a=C(n,i),b=p2[i],c=B(i,i);
int s1=1ll*a*b%mod*c%mod;
b=p1[i-k],c=C(i-1,i-k);
int s2=1ll*b*c%mod;
ret=(ret+1ll*s1*s2%mod)%mod;
}
printf("%lld\n",1ll*l*ret%mod);
}