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]\) 的权值相减即可。
考虑答案:
如果考虑转移的话,那么就可以展开第 \(n\) 项得到:
因为 \(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}\),但是在第三项出现了问题,我们单独将它拿出来审视,设:
考虑它的转移,也拆开第 \(n\) 项来分析:
然后发现,\(x_na_n\),\(x_n^2\) 与前边项无关,直接统计即可,观察最后一项其实就是 \(x_ng_{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)]\)
如果过了终止作用时间,使它不再增长即可
所以一个覆盖的贡献为:
将 \(i\) 提取出来得到:
然后询问和修改就分离开来了,维护几颗线段树考虑最小值分别取什么即可
细节代码中有写一些
点击查看代码
#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]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?