CSP 模拟 2

感觉像是 noi 模拟赛多了个 p

T1 F

咋做都行,但是考场上的正确做法被后来优化 RE 了,痛失 60pts
其中一种做法是考虑只有 \(a_1\oplus b_i\) 有可能成为答案,然后验证即可

T2 S

定义 dp 状态 \(f_{i,j,k,0/1/2}\) 为用了 \(i\) 个红球,\(j\) 个绿球,\(k\) 个红球,并且最后一位是什么球

相同颜色的球交换无意义,所以最后的序列与一开始相对位置相同,最后的交换次数即为逆序对数,所以单次转移的代价即为这个球贡献的逆序对数

点击查看代码
#include<bits/stdc++.h>
#define N 405
#define inf 0x3f3f3f3f
using namespace std;

int n,f[2][N][N][3];
int sum[N][3],pos[N][3];
char s[N];
// 🤣👉🤡
int main(){
    freopen("s.in","r",stdin);
    freopen("s.out","w",stdout);
    cin>>n;scanf("%s",s+1);
    for(int i=1;i<=n;i++){
        sum[i][0]=sum[i-1][0]+(s[i]=='R');
        sum[i][1]=sum[i-1][1]+(s[i]=='G');
        sum[i][2]=sum[i-1][2]+(s[i]=='Y');
        if(s[i]=='R') pos[sum[i][0]][0]=i;
        if(s[i]=='G') pos[sum[i][1]][1]=i;
        if(s[i]=='Y') pos[sum[i][2]][2]=i;
    }
    memset(f,0x3f,sizeof(f));
    f[0][0][0][0]=f[0][0][0][1]=f[0][0][0][2]=0;
    for(int i=0;i<=sum[n][0];i++){
        int now=i&1,lst=(i&1)^1;
        for(int j=0;j<=sum[n][1];j++)
            for(int k=0;k<=sum[n][2];k++){
                if(!i&&!j&&!k) continue;
                memset(f[now][j][k],0x3f,sizeof(f[now][j][k]));
                if(i){
                    int tmp=min(f[lst][j][k][1],f[lst][j][k][2]);
                    if(tmp!=inf) f[now][j][k][0]=tmp+(sum[pos[i][0]][1]-j>0?sum[pos[i][0]][1]-j:0)+(sum[pos[i][0]][2]-k>0?sum[pos[i][0]][2]-k:0);
                }
                if(j){
                    int tmp=min(f[now][j-1][k][0],f[now][j-1][k][2]);
                    if(tmp!=inf) f[now][j][k][1]=tmp+(sum[pos[j][1]][0]-i>0?sum[pos[j][1]][0]-i:0)+(sum[pos[j][1]][2]-k>0?sum[pos[j][1]][2]-k:0);
                }
                if(k){
                    int tmp=min(f[now][j][k-1][0],f[now][j][k-1][1]);
                    if(tmp!=inf) f[now][j][k][2]=tmp+(sum[pos[k][2]][0]-i>0?sum[pos[k][2]][0]-i:0)+(sum[pos[k][2]][1]-j>0?sum[pos[k][2]][1]-j:0);
                }
            }
    }
    int ans=min({f[sum[n][0]&1][sum[n][1]][sum[n][2]][0],f[sum[n][0]&1][sum[n][1]][sum[n][2]][1],f[sum[n][0]&1][sum[n][1]][sum[n][2]][2]});
    cout<<ans;
}

T3 Y

原题(实际上就改了模数和传递球的方向)

设操作序列为 \(x_i\),即为每个人向右传递的球数,则传完球的序列 \(b_i=a_i-x_i+x_{i-1}\),发现后边其实是 \(x_i\) 的差分,所以答案只与差分相关,所以我们只统计 \(\min x_i=0\) 的操作序列,因为所有 \(\min x_i>0\) 的情况都可以通过使所有 \(x_i\) 减去 \(\min x_i\) 来实现转化,如果统计 \(\min x_i=0\) 的话,只需要容斥一下,分别统计所有 \(x_i\in[0,a_i]\) 的情况和所有 \(x_i\in[1,a_i]\) 的权值相减即可。

考虑答案:

\[f_n=\prod_{i=1}^n (a_i-x_i+x_{i-1}) \]

如果考虑转移的话,那么就可以展开第 \(n\) 项得到:

\[a_n\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1})-x_n\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1})+x_{n-1}\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1}) \]

因为 \(a_n\)\(x_n\) 与前边项无关,又因为 \(x_n\in [0,a_i]\),所以前两项为 \(a_nf_{n-1}\)\(\left(\sum_{i=0}^{a_n}i\right)\cdot f_{n-1}\),但是在第三项出现了问题,我们单独将它拿出来审视,设:

\[g_n=x_n\prod_{i=1}^n (a_i-x_i+x_{i-1}) \]

考虑它的转移,也拆开第 \(n\) 项来分析:

\[x_na_n\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1})-x_n^2\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1})+x_nx_{n-1}\prod_{i=1}^{n-1} (a_i-x_i+x_{i-1}) \]

然后发现,\(x_na_n\)\(x_n^2\) 与前边项无关,直接统计即可,观察最后一项其实就是 \(x_ng_{n-1}\),从之前转移即可

将前边的两个递推公式综合起来可得:

\[f_n=S_1(a_n)f_{n-1}+(a_n+1)g_{n-1} \]

\[g_n=(a_nS_1(a_n)-S_2(a_n))f_{n-1}+S_1(a_n)g_{n-1} \]

然后枚举断点情况和 \(x_i\) 值域情况容斥即可

点击查看代码
#include<bits/stdc++.h>
#define N 1000005
#define int long long
using namespace std;

const int p=1e9+7;
int n,a[N],ans,f[N][2];
int inv2=500000004,inv6=166666668;
int s(int x,bool typ){
    if(!typ) return x*(x+1)%p*inv2%p;
    else return x*(x+1)%p*(x*2+1)%p*inv6%p;
}
int calc(bool typ1,bool typ2){
    memset(f,0,sizeof(f));
    f[1][1]=typ1,f[1][0]=typ1^1;
    for(int i=1;i<=n;i++){
        int nxt=i%n+1;
        f[nxt][0]=(f[nxt][0]+f[i][0]*s(a[i]-typ2,0)%p+f[i][1]*(a[i]-typ2+1)%p)%p;
        f[nxt][1]=(f[nxt][1]+f[i][0]*((a[i])*s(a[i],0)%p-s(a[i],1)+p)%p+f[i][1]*s(a[i],0)%p)%p;
    }
    return f[1][typ1]-1;
}

signed main(){
    freopen("y.in","r",stdin);
    freopen("y.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++) 
        scanf("%lld",a+i);
    cout<<((calc(1,0)+calc(0,0))%p-(calc(0,1)+calc(1,1))%p+p)%p;
}

T4 O

原题(题目背景都基本一样)

考虑每个 \(i\) 最先能被覆盖的时间和该覆盖影响结束的时间

\(L_p\) 为左侧第一个大于 \(p\) 的数的位置,\(R_p\) 为右边第一个大于等于 \(p\) 的数(避免算重)

则这个覆盖的权值 \(d\)\(S_{L_p}-S_{p}\),开始作用的时间 \(t\)\(p-L_p\),终止作用的时间为 \(R_p-L_p\)

容易得出,在终止作用时间前开始作用后,这个覆盖在时刻 \(i\) 作用的区间为 \([p,p-t+i]\)

所以对于时刻 \(i\) 查询前 \(x\) 的前缀和,每个覆盖贡献的区间为 \([\min(p,x),\min(p-t+i,x)]\)

如果过了终止作用时间,使它不再增长即可

所以一个覆盖的贡献为:

\[d \cdot (\min(p-t+i,x)-\min(p,x)+1) \]

\(i\) 提取出来得到:

\[d \cdot (i+\min(p-t,x-i)-\min(p,x)+1) \]

然后询问和修改就分离开来了,维护几颗线段树考虑最小值分别取什么即可

细节代码中有写一些

点击查看代码
#include<bits/stdc++.h>
#define N 200005
#define lc (pos<<1)
#define rc (pos<<1|1)
// #define mid ((l+r)>>1)
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,SIZE,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+SIZE&&(flush(),p3=out),*p3++=(x))
#define int long long
#define mp make_pair
#define pii pair<int,int>
using namespace std;

int a[N],ans[N];
signed maxn[N<<3],flag=true;
void build(int pos,int l,int r){
    if(l==r){
        maxn[pos]=a[l];
        return;
    }
    signed mid=(l+r)>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    maxn[pos]=max(maxn[lc],maxn[rc]);
}
int query(int pos,int l,int r,int L,int R){
    if(r<L||R<l) return 0;
    if(L<=l&&r<=R) return maxn[pos];
    signed mid=(l+r)>>1;
    return max(query(lc,l,mid,L,R),query(rc,mid+1,r,L,R));
}
signed top,stk[N],L[N],R[N],n,Q;
struct node{signed typ,p,t,d;};
struct Node{signed t,l,r;}que[N];
vector<node> opt[N];
vector<pii> q[N];
struct SMT{
    int sum[N<<3],lz[N<<3];
    inline void pushdown(signed pos,signed l,signed r){
        if(!lz[pos]) return;
        signed mid=(l+r)>>1;
        sum[lc]+=(int)(mid-l+1)*lz[pos];
        sum[rc]+=(int)(r-mid)*lz[pos];
        lz[lc]+=lz[pos];
        lz[rc]+=lz[pos];
        lz[pos]=0;
    }
    void update(signed pos,signed l,signed r,signed L,signed R,int val){
        if(L<=l&&r<=R){
            lz[pos]+=val;
            sum[pos]+=(int)(r-l+1)*val;
            return;
        }
        pushdown(pos,l,r);
        signed mid=(l+r)>>1;
        if(L<=mid) update(lc,l,mid,L,R,val);
        if(R>mid) update(rc,mid+1,r,L,R,val);
        sum[pos]=sum[lc]+sum[rc];
    }
    int query(signed pos,signed l,signed r,signed L,signed R){
        if(L<=l&&r<=R) return sum[pos];
        pushdown(pos,l,r);
        int tmp=0;
        signed mid=(l+r)>>1;
        if(L<=mid) tmp+=query(lc,l,mid,L,R);
        if(R>mid) tmp+=query(rc,mid+1,r,L,R);
        return tmp;
    }
}T1,T2,T3,T4,C;
constexpr auto SIZE(1<<22);
char in[SIZE],out[SIZE],*p1=in,*p2=in,*p3=out;
class Flush{public:~Flush(){flush();}}_;
inline int read(){
	int x(0);char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar());
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return x;
}
inline void write(int x){
	static short Sta[50],top(0);
	do{Sta[++top]=x%10;x/=10;}while(x);
	for(;top;) putchar(Sta[top--]|48);
	putchar('\n');
}

signed main(){
    freopen("o.in","r",stdin);
    freopen("o.out","w",stdout);
    cin>>n>>Q;
    for(register signed i=1;i<=n;++i) a[i]=read();
    for(register signed i=1;i<=Q;++i){
        signed t=read(),l=read(),r=read();
        q[t].push_back(mp(l-1,-i));
        q[t].push_back(mp(r,i));
        que[i]=(Node){t,l,r};
        if(l!=r) flag=false;
    }
    if(flag){
        build(1,1,n);
        for(int i=1;i<=Q;i++)
            printf("%lld\n",query(1,1,n,max(que[i].l-que[i].t,1),que[i].l));
        return 0;
    }
    // cout<<"1"<<endl;
    for(register signed i=1;i<=n;++i){
        for(;top&&a[i]>=a[stk[top]];) R[stk[top--]]=i;
        L[i]=stk[top];stk[++top]=i;
    }
    for(;top;) R[stk[top--]]=n+1;
    for(register signed i=1;i<=n;++i)
        if(L[i]){
            signed t=i-L[i],d=a[L[i]]-a[i],te=R[i]-i;
            opt[t].push_back((node){1,i,t,d});
            opt[t+te].push_back((node){-1,i,t,d});
        }
    // cout<<"3"<<endl;
    for(register signed i=2;i<=n;++i) a[i]+=a[i-1];
    for(register signed i=1;i<=n;++i){
        // cout<<i<<":1"<<endl;
        for(node tmp:opt[i]){
            T1.update(1,-n,n,tmp.p,tmp.p,tmp.typ*tmp.d);
            T2.update(1,-n,n,tmp.p-tmp.t,tmp.p-tmp.t,tmp.typ*tmp.d);
            T3.update(1,-n,n,tmp.p,tmp.p,(int)tmp.typ*tmp.p*tmp.d);
            T4.update(1,-n,n,tmp.p-tmp.t,tmp.p-tmp.t,(int)tmp.typ*(tmp.p-tmp.t)*tmp.d);
            if(tmp.typ==-1) C.update(1,1,n,tmp.p,tmp.p-tmp.t+i-1,(int)tmp.d);
        }
        // cout<<i<<":2"<<endl;
        for(pii tmp:q[i]){
            signed x=tmp.first; if(!x) continue;
            int temp=(int)i*T1.sum[1]+a[x]; // 第一个是 d*i ,又因为维护的是差值,所以补全前缀和
            // 考虑 d(min(p-t,x-i)-min(p,x)) 分别取什么
            temp-=T3.query(1,-n,n,-n,x); // min(p,x) 取 p,求出所有满足条件的 $p*d$ 之和
            temp-=T1.query(1,-n,n,x+1,n)*x; // min(p,x) 取 x,求出所有满足条件的 $d$ 之和乘上 $x$
            temp+=T4.query(1,-n,n,-n,x-i); // min(p-t,x-i) 取 p-t, 求出所有满足此条件的 $d*(p-t)$
            temp+=T2.query(1,-n,n,x-i+1,n)*(x-i); // min(p-t,x-i) 取 x-i,求出所有满足条件的 $d$
            temp+=C.query(1,1,n,1,x); // 因为如果过了边所影响的终止时间的话,不再变化,变为常函数,对能影响前缀和范围的常函数求和
            temp+=T1.query(1,-n,n,-n,x); // 求出能影响该点的边的区间扩展 1
            if(tmp.second>0) ans[tmp.second]+=temp;
            else ans[-tmp.second]-=temp;
        }
    }
    for(int i=1;i<=Q;i++) write(ans[i]);
}
posted @   Rolling_star  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示