[CSP-S模拟测试52]题解
A.平均数
看到第K小,又确定跟平衡树/主席树没有关系,可以把问题转化为有K-1个答案比它小再考虑二分。
二分平均值x,之后将原序列统一减去x。这时序列中区间和<0的区间个数就是原序列中平均值小于x的区间个数。
求个前缀和,那么区间和<0转化成$sum_l > sum_r$,归并排序求逆序对即可。复杂度$O(n\ log^2 \ n)$
#include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<algorithm> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=1e5+5; const double eps=1e-8; int n,K,num; double a[N],b[N],sum[N],c[N]; void merge(int l,int r) { if(l==r)return ; int mid=l+r>>1; merge(l,mid);merge(mid+1,r); int i=l,j=mid+1,k=l; while(i<=mid&&j<=r) { if(sum[i]>sum[j])num+=mid-i+1,c[k]=sum[j],j++; else c[k]=sum[i],i++; k++; } while(i<=mid)c[k]=sum[i],i++,k++; while(j<=r)c[k]=sum[j],j++,k++; for(int i=l;i<=r;i++)sum[i]=c[i]; } bool check(double val) { sum[0]=0;num=0; for(int i=1;i<=n;i++) a[i]=b[i]-val,sum[i]=sum[i-1]+a[i]; merge(0,n); return num<K; } int main() { n=read();K=read(); double l=0,r=0; for(int i=1;i<=n;i++) b[i]=(double)read(); r=1e9+1.0; while(r-l>eps) { double mid=(l+r)/2.0; if(check(mid))l=mid; else r=mid; } printf("%.4lf\n",l); return 0; }
B.涂色游戏
不会。勉强理解题解之后过掉的。矩阵快速幂一直不能游刃有余地运用到dp优化里,这布星啊。
最裸的矩阵快速幂。
这一行的填色方案只与上一行有关,所以dp。
$dp[i][j]$表示在第i行填了特定j中颜色的方案数。
考虑转移。枚举两行的颜色数再枚举交集即可。
如果上一行有i种下一行有j种交集为k种。
转移条件是$i+j-k<=p\ and\ i+j-k>=q$。
那么就是一个组合数学问题了。
先在上一行的颜色里选出重复部分C
再选出这一行交集以外的部分C
然后的问题就是已知某k种颜色填n个位置,要求每种颜色必须出现。我是dp做的,据说可以容斥。
然后发现每一层的转移系数都相同,那就是简单的矩阵快速幂了。
注意取模。
——DeepinC
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const ll mod=998244353; const int N=105,M=2005; int n,p,q; ll m,C[N][N],A[N][N],b[N][N],res[N],tmp[N][N],tp[N]; void b_mult() { memset(tmp,0,sizeof(tmp)); for(int i=1;i<=p;i++) for(int j=1;j<=p;j++) for(int k=1;k<=p;k++) (tmp[i][j]+=b[i][k]*b[k][j]%mod)%=mod; for(int i=1;i<=p;i++) for(int j=1;j<=p;j++) b[i][j]=tmp[i][j]; } void res_mult() { memset(tp,0,sizeof(tp)); for(int i=1;i<=p;i++) for(int j=1;j<=p;j++) (tp[i]+=res[j]*b[j][i]%mod)%=mod; for(int i=1;i<=p;i++) res[i]=tp[i]; } int main() { scanf("%d%lld%d%d",&n,&m,&p,&q); C[0][0]=A[0][0]=1; for(int i=1;i<=n;i++) for(int j=1;j<=p;j++) A[i][j]=(A[i-1][j-1]+A[i-1][j])*j%mod; for(int i=1;i<=p;i++) { C[i][0]=1; for(int j=1;j<=i;j++) (C[i][j]=C[i-1][j-1]+C[i-1][j])%=mod; } for(int i=1;i<=p;i++) for(int j=1;j<=p;j++) for(int k=0;k<=p;k++) if(i+j-k<=p&&i+j-k>=q) (b[i][j]+=C[i][k]*C[p-i][j-k]%mod*A[n][j])%=mod; for(int i=1;i<=p;i++) res[i]=A[n][i]*C[p][i]%mod; m--; while(m) { if(m&1)res_mult(); b_mult(); m>>=1; } ll ans=0; for(int i=1;i<=p;i++) (ans+=res[i])%=mod; printf("%lld\n",ans); return 0; }
C.序列
先用主席树求第一问。
我们注意到,每次修改后对答案作贡献其实只有范围包括修改点的询问,并且改小了做正贡献,改大了做负贡献。
对于询问建立一棵线段树,下标为询问控制的区间,对每一个结点开一个vector,把所询问的值添加进线段树对应的节点。
将询问按询问值排序,之后按顺序全部更新到线段树中。排序是为了让每个节点的vector内部元素保证有序,确保可以二分查找。
之后对于每次修改,在线段树里查一下改之前该位置的贡献(单点查询,路过每个包含的区间时在它的vector里二分查找),再查一下改之后的,二者做差更新答案,然后在外部数组里更改即可。
复杂度$O(n\ log^2 \ n)$。
#include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<algorithm> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=1e5+5; typedef long long ll; int n,m,Q,a[N]; struct ques { int l,r,val; friend bool operator < (ques x,ques y) { return x.val<y.val; } }q[N]; int ls[N*40],rs[N*40],size[N*40],type,root[N]; ll ans; void update(int &k,int l,int r,int old,int val) { k=++type; ls[k]=ls[old]; rs[k]=rs[old]; size[k]=size[old]+1; if(l==r)return ; int mid=l+r>>1; if(val<=mid)update(ls[k],l,mid,ls[old],val); else update(rs[k],mid+1,r,rs[old],val); } int query(int k,int l,int r,int val) { if(l==r)return size[k]; int mid=l+r>>1,res=0; if(val<=mid)return size[rs[k]]+query(ls[k],l,mid,val); else return query(rs[k],mid+1,r,val); } vector<int> w[N<<2]; #define ls(k) (k)<<1 #define rs(k) (k)<<1|1 void add(int k,int l,int r,int L,int R,int val) { if(L<=l&&R>=r) { w[k].push_back(val); return ; } int mid=l+r>>1; if(L<=mid)add(ls(k),l,mid,L,R,val); if(R>mid)add(rs(k),mid+1,r,L,R,val); } int ask(int k,int l,int r,int pos,int val) { int res=upper_bound(w[k].begin(),w[k].end(),val)-w[k].begin()-1; if(l==r)return res; int mid=l+r>>1; if(pos<=mid)res+=ask(ls(k),l,mid,pos,val); else res+=ask(rs(k),mid+1,r,pos,val); return res; } int main() { n=read();m=read();Q=read(); for(int i=1;i<=n;i++) a[i]=read(),update(root[i],1,n,root[i-1],a[i]); for(int i=1;i<=m;i++) { q[i].l=read(),q[i].r=read(),q[i].val=read(); int res1=query(root[q[i].r],1,n,q[i].val),res2=query(root[q[i].l-1],1,n,q[i].val); ans+=res1-res2; } sort(q+1,q+m+1); for(int i=1;i<=m;i++) add(1,1,n,q[i].l,q[i].r,q[i].val); printf("%lld\n",ans); while(Q--) { int p=read()^ans,v=read()^ans; int res1=ask(1,1,n,p,a[p]),res2=ask(1,1,n,p,v); ans+=res2-res1; a[p]=v; printf("%lld\n",ans); } return 0; }