【比赛记录】2025CSP-S模拟赛20
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 20 | 20 | - | - | 40 | 20/21 |
比赛是💩,我更是💩。
A. 签
可以发现每次操作会减少 \(3\) 个逆序对,同时如果只考虑奇数位或偶数位那么会减少 \(1\) 个逆序对。因此有解的充要条件是奇数逆序对与偶数逆序对之和等于总逆序对的三分之一。具体证明不会
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int T,n,a[maxn];
struct{
int tr[maxn];
il int lowbit(int x){
return x&-x;
}
il void init(){
memset(tr,0,sizeof(tr));
}
il void add(int p,int v){
for(;p<=n;p+=lowbit(p)){
tr[p]+=v;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
}F;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n;
bool flag=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]%2!=i%2){
flag=1;
}
}
if(flag){
cout<<"No\n";
continue;
}
ll c1=0,c2=0,c=0;
F.init();
for(int i=1;i<=n;i+=2){
c1+=F.query(n)-F.query(a[i]);
F.add(a[i],1);
}
F.init();
for(int i=2;i<=n;i+=2){
c2+=F.query(n)-F.query(a[i]);
F.add(a[i],1);
}
F.init();
for(int i=1;i<=n;i++){
c+=F.query(n)-F.query(a[i]);
F.add(a[i],1);
}
cout<<((c1+c2)*3==c?"Yes":"No")<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
B. 数据结构基础练习题
首先将操作离线下来,将修改操作差分,在 \(l\) 处加,在 \(r+1\) 处减。
我们对序列 \(a\) 的下标做扫描线,维护每个时刻当前位置的值。于是对于每个询问,我们将下标小于它的和同一下标、时间更小的修改全部插入进去即可。
问题于是转化为区间加操作和区间查询第 \(k\) 小。考虑分块,维护每一块中排序后的序列,这样就可以算出区间中小于某个数的数的数量,再套个二分即可。
理论最佳快长应该是 \(631\),然额比 \(300\) 的块长多跑了 4000ms。
Code
#include<bits/stdc++.h>
#define il inline
#define lwrb lower_bound
using namespace std;
namespace asbt{
const int maxn=7e4+5;
int n,m,ca,cb,ans[maxn];
bool vis[maxn];
struct node{
int t,p,k,l,r;
node(int t=0,int p=0,int k=0,int l=0,int r=0):t(t),p(p),k(k),l(l),r(r){}
il bool operator<(const node &x)const{
return p<x.p||p==x.p&&t<x.t;
}
}a[maxn<<1],b[maxn];
struct{
int B=300,bn,L[240],R[240],bel[maxn];
int p[240][305],q[240][305],tag[240];
il void reset(int x){
int len=R[x]-L[x]+1;
for(int i=1;i<=len;i++){
q[x][i]=p[x][i];
}
sort(q[x]+1,q[x]+len+1);
}
il void build(){
L[0]=R[0]=-1;
while(R[bn]!=m){
bn++;
L[bn]=R[bn-1]+1;
R[bn]=min(R[bn-1]+B,m);
for(int i=L[bn];i<=R[bn];i++){
bel[i]=bn;
}
}
}
il void add(int l,int r,int v){
int pl=bel[l],pr=bel[r];
if(pl==pr){
for(int i=l;i<=r;i++){
p[pl][i-L[pl]+1]+=v;
}
reset(pl);
return ;
}
for(int i=l;i<=R[pl];i++){
p[pl][i-L[pl]+1]+=v;
}
for(int i=L[pr];i<=r;i++){
p[pr][i-L[pr]+1]+=v;
}
reset(pl),reset(pr);
for(int i=pl+1;i<pr;i++){
tag[i]+=v;
}
}
il int query(int l,int r,int x){
int pl=bel[l],pr=bel[r],res=0;
if(pl==pr){
for(int i=l;i<=r;i++){
if(p[pl][i-L[pl]+1]+tag[pl]<x){
res++;
}
}
return res;
}
for(int i=l;i<=R[pl];i++){
if(p[pl][i-L[pl]+1]+tag[pl]<x){
res++;
}
}
for(int i=L[pr];i<=r;i++){
if(p[pr][i-L[pr]+1]+tag[pr]<x){
res++;
}
}
for(int i=pl+1,len;i<pr;i++){
len=R[i]-L[i]+1;
res+=lwrb(q[i]+1,q[i]+len+1,x-tag[i])-q[i]-1;
}
return res;
}
il int kth(int l,int r,int k){
int ll=-7e7,rr=7e7;
while(ll<rr){
int mid=(ll+rr+1)>>1;
if(query(l,r,mid)<k){
ll=mid;
}
else{
rr=mid-1;
}
}
return ll;
}
}BL;
int main(){
// system("fc ds3.ans my.out");
// freopen("ds4.in","r",stdin);
// freopen("my.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,opt,p,l,r,k;i<=m;i++){
cin>>opt;
if(!opt){
cin>>l>>r>>k;
a[++ca]=node(i,l,k);
a[++ca]=node(i,r+1,-k);
}
else{
cin>>p>>l>>r>>k;
b[++cb]=node(i,p,k,l,r);
vis[i]=1;
}
}
sort(a+1,a+ca+1),sort(b+1,b+cb+1);
BL.build();
for(int i=1,j=1;i<=cb;i++){
// cerr<<"6666\n";
while(j<=ca&&a[j]<b[i]){
BL.add(a[j].t,m,a[j].k);
j++;
}
ans[b[i].t]=BL.kth(b[i].l,b[i].r,b[i].k);
}
for(int i=1;i<=m;i++){
if(vis[i]){
cout<<ans[i]<<"\n";
}
}
return 0;
}
}
int main(){return asbt::main();}
C. 制糊串
一个直接的想法是设 \(f_{i,0/1,j}\) 表示在 \(i\) 位置填 \(A/B\) 能否使答案为 \(j\)。可以发现 \(f_{i,0/1}\) 对应的 \(01\) 串中只有一段连续的 \(1\),或者顶多中间被挖去了一个位置。这可以用归纳法证明。
于是可以用一个三元组 \((l,r,p)\) 来表示一个 \(01\) 串,即左、右端点为 \(l\)、\(r\),被挖去的那个位置为 \(p\)。于是时间复杂度线性。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int T,n,m;
string s;
struct node{
int l,r,p;
node(int l=0,int r=0,int p=-1):l(l),r(r),p(p){}
il bool ctain(const int &x)const{
return x>=l&&x<=r&&x!=p;
}
il node operator<<(const int &x)const{
return node(l+x,r+x,~p?p+x:p);
}
il node operator|(const node &x)const{
int L=min(l,x.l),R=max(r,x.r);
if(~p&&!x.ctain(p)){
return node(L,R,p);
}
if(~x.p&&!ctain(x.p)){
return node(L,R,x.p);
}
if(x.l==r+2){
return node(L,R,r+1);
}
if(l==x.r+2){
return node(L,R,x.r+1);
}
return node(L,R);
}
}f[maxn][2];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m>>s;
s=" "+s;
for(int i=1;i<n;i++){
if(s[i]!=s[i+1]){
m--;
}
}
if(s[1]=='A'){
f[1][0]=node(0,0),f[1][1]=node(1,1);
}
else{
f[1][0]=node(1,1),f[1][1]=node(0,0);
}
for(int i=2;i<=n;i++){
if(s[i]=='A'){
f[i][0]=f[i-1][0]|f[i-1][1]<<1;
f[i][1]=f[i-1][0]<<2|f[i-1][1]<<1;
}
else{
f[i][0]=f[i-1][0]<<1|f[i-1][1]<<2;
f[i][1]=f[i-1][0]<<1|f[i-1][1];
}
}
// for(int i=1;i<=n;i++){
// cout<<f[i][0].l<<" "<<f[i][0].r<<" "<<f[i][0].p<<"\n";
// cout<<f[i][1].l<<" "<<f[i][1].r<<" "<<f[i][1].p<<"\n";
// }
cout<<(f[n][0].ctain(m)||f[n][1].ctain(m)?"YES":"NO")<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
D. 牛仔
发现牛仔序列这个限制较为困难。考虑正难则反,用不考虑这个限制的答案减去不符合这个限制的答案。显然前者为 \((n-m+1)\times k^{n-m}\)。然后就需要分类讨论了。
1.\(A\) 为牛仔序列
这个东西比较显然,没有不符合限制的序列。
2.\(A\) 中有数重复出现
较为明显的是,跨过 \(A\) 的子序列不可能是 \(1\) 到 \(K\) 的排列。因此只用考虑 \(A\) 之前与 \(A\) 之后。设 \(f_{i,j}\) 表示长为 \(i\) 的序列、结尾最长的互不相同的序列长为 \(j\) 的方案数。转移有两种:
-
往后面加一个新的数,即 \(f_{i,j}\times(K-j)\to f_{i+1,j+1}\)
-
往后加一个已有的数,从而缩短结尾序列的长度,即 \(f_{i,j}\to f_{i+1,p}\),其中 \(p\le j\)。
显然第二个转移可以后缀和优化。往前 DP 一遍往后 DP 一遍,统计答案即可。
3.\(A\) 中没有重复的数
可以发现 \(A_K^m\) 种序列是等价的,考虑计算长为 \(n\) 的非牛仔序列中长为 \(m\) 的不重复子序列的数量,除以 \(A_K^m\) 即为牛度。DP 思路是类似的,需要另外设一个 \(g_{i,j}\) 表示牛度,转移依然类似,只需要在 \(j\ge m\) 时加上 \(f\) 的贡献即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=25005,mod=1e9+7;
int n,kk,m,a[maxn],tong[405];
int f[maxn][405],g[maxn][405];
int sf[maxn][405],sg[maxn][405];
int fac[maxn],inv[maxn];
il int qpow(int x,int y=mod-2){
int res=1;
for(;y;y>>=1,x=x*1ll*x%mod){
if(y&1){
res=res*1ll*x%mod;
}
}
return res;
}
il bool chk1(){
for(int l=1,r=kk;r<=m;l++,r++){
for(int i=1;i<=kk;i++){
tong[i]=0;
}
for(int i=l;i<=r;i++){
if(tong[a[i]]++==1){
goto togo;
}
}
return 1;
togo:;
}
return 0;
}
il bool chk2(){
for(int i=1;i<=kk;i++){
tong[i]=0;
}
for(int i=1;i<=m;i++){
if(tong[a[i]]++==1){
return 1;
}
}
return 0;
}
il void DP(int f[maxn][405],int sf[maxn][405]){
for(int i=0;i<=n;i++){
for(int j=kk-1;j;j--){
sf[i][j]=(sf[i][j+1]+f[i][j])%mod;
}
for(int j=1;j<kk;j++){
(f[i+1][j+1]+=(kk-j)*1ll*f[i][j]%mod)%=mod;
(f[i+1][j]+=sf[i][j])%=mod;
}
}
}
il void init(int x=25000){
fac[0]=1;
for(int i=1;i<=x;i++){
fac[i]=fac[i-1]*1ll*i%mod;
}
inv[x]=qpow(fac[x]);
for(int i=x;i;i--){
inv[i-1]=inv[i]*1ll*i%mod;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>kk>>m;
for(int i=1;i<=m;i++){
cin>>a[i];
}
int ans=qpow(kk,n-m)*1ll*(n-m+1)%mod;
if(chk1()){
cout<<ans;
return 0;
}
if(chk2()){
// puts("666");
for(int i=1;i<=kk;i++){
tong[i]=0;
}
for(int i=1;i<=m;i++){
if(tong[a[i]]++==1){
f[0][i-1]=1;
break;
}
}
DP(f,sf);
for(int i=1;i<=kk;i++){
tong[i]=0;
}
for(int i=m;i;i--){
if(tong[a[i]]++==1){
g[0][m-i]=1;
break;
}
}
DP(g,sg);
for(int i=0,j=n-m;~j;i++,j--){
(ans+=mod-sf[i][1]*1ll*sg[j][1]%mod)%=mod;
}
cout<<ans;
return 0;
}
init();
f[0][0]=sf[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<kk;j++){
f[i][j]=(f[i-1][j-1]*1ll*(kk-j+1)+sf[i-1][j])%mod;
g[i][j]=(g[i-1][j-1]*1ll*(kk-j+1)+sg[i-1][j])%mod;
if(j>=m){
(g[i][j]+=f[i][j])%=mod;
}
}
for(int j=kk-1;j;j--){
sf[i][j]=(sf[i][j+1]+f[i][j])%mod;
sg[i][j]=(sg[i][j+1]+g[i][j])%mod;
}
}
ans-=sg[n][1]*1ll*fac[kk-m]%mod*inv[kk]%mod;
cout<<(ans+mod)%mod;
return 0;
}
}
int main(){return asbt::main();}

浙公网安备 33010602011771号