冲刺CSP联训模拟2
冲刺CSP联训模拟2
题目来源:正睿 \(noip2023\) 赛前 \(20\) 天冲刺集训 \(day16(2023.11.2)\)
\(T1\) P294. 挤压 \(40pts\)
-
部分分
- \(20 \%\) :爆搜,时间复杂度为 \(O(2^{n})\) 。
- 另外 \(20 \%\) :观察到值域较小,将值域计入状态设计,时间复杂度为 \(O(nV)\) 。
点击查看代码
const ll mod=1000000007; ll a[100010],p[100010],pp[100010],q[100010],f[2][9000010],ans=0; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs(ll pos,ll n,ll sum,ll mul) { if(pos==n+1) { sum%=mod; ans=(ans+(sum*sum%mod)*mul%mod)%mod; } else { dfs(pos+1,n,sum^a[pos],mul*p[pos]%mod); dfs(pos+1,n,sum,mul*pp[pos]%mod); } } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); ll n,m=0,inv=qpow(1000000000,mod-2,mod),i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; m=max(m,a[i]); } for(i=1;i<=n;i++) { cin>>q[i]; p[i]=q[i]*inv%mod; pp[i]=(1000000000-q[i])*inv%mod; } if(n<=28) { dfs(1,n,0,1); } else { m*=2; f[0][0]=1; for(i=1;i<=n;i++) { for(j=0;j<=m;j++) { f[i&1][j]=(f[(i-1)&1][j]*pp[i]%mod+f[(i-1)&1][j^a[i]]*p[i]%mod)%mod; } } for(i=0;i<=m;i++) { ans=(ans+((i%mod)*(i%mod)%mod)*f[n&1][i]%mod)%mod; } } cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }
-
正解
- 按位做,考虑拆平方。
- 设最终的异或和在二进制表示下(空位补零)为 \((s_{32}s_{31} \dots s_{1}s_{0})_{2}\) ,其平方可以表示成 \(\sum\limits_{i=0}^{32}\sum\limits_{j=0}^{32}s_{i}s_{j}2^{i+j}\) 。
- 观察到每一项仅与两位有关,那么可以将 \(a_{x}\) 的第 \(i,j\) 位拿出来单独计算其期望。
- 设 \(f_{k,i,j,0/1,0/1}\) 表示处理到第 \(k\) 个数时,第 \(i\) 位 \(=0/1\) 且第 \(j\) 位 \(=0/1\) 的期望,枚举选或不选转移即可。
- 最终,有 \(\sum\limits_{i=0}^{32}\sum\limits_{j=0}^{32}f_{n,i,j,1,1}2^{i+j}\) 即为所求。
点击查看代码
const ll mod=1000000007; ll a[100010],p[100010],q[100010],f[2][35][35][2][2]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); ll n,inv=qpow(1000000000,mod-2,mod),ans=0,i,j,k,h,l; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { cin>>q[i]; p[i]=q[i]*inv%mod; } for(i=0;i<=32;i++) { for(j=0;j<=32;j++) { f[0][i][j][0][0]=1; } } for(k=1;k<=n;k++) { for(i=0;i<=32;i++) { for(j=0;j<=32;j++) { for(h=0;h<=1;h++) { for(l=0;l<=1;l++) { f[k&1][i][j][h][l]=(f[(k-1)&1][i][j][h][l]*(1-p[k]+mod)%mod+f[(k-1)&1][i][j][h^((a[k]>>i)&1)][l^((a[k]>>j)&1)]*p[k]%mod); } } } } } for(i=0;i<=32;i++) { for(j=0;j<=32;j++) { ans=(ans+f[n&1][i][j][1][1]*qpow(2,i+j,mod)%mod)%mod; } } cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }
\(T2\) P295. 工地难题 \(25pts\)
-
弱化版: Gym103428M 810975
- 需要特判 \(k \le m \le n\) 时才可能存在解,当 \(k=0\) 时输出 \((m=0)\) 。
-
当 \(n \ne m\) 时,对于 \(k> \frac{m}{2}\) ,答案显然为 \(2\dbinom{n-(k+1)}{m-k}+\dbinom{n-(k+2)}{m-k}A_{n-(k+2)+1}^{1}=2\dbinom{n-k-1}{m-k}+\dbinom{n-k-2}{m-k}(n-k-1)\) 。
-
部分分
- \(20 \%\) :爆搜,递归枚举组合,并及时剪枝。
点击查看代码
const ll p=1000000007; ll inv[200010],jc[200010],jc_inv[200010],f[200010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0; } void dfs(ll pos,ll n,ll sum0,ll sum1,ll maxx,ll pre,ll k) { maxx=max(maxx,pre); if(maxx<=k) { if(pos==n+1) { f[maxx]=(f[maxx]+1)%p; return; } else { if(sum0>=1) { dfs(pos+1,n,sum0-1,sum1,maxx,0,k); } if(sum1>=1) { dfs(pos+1,n,sum0,sum1-1,maxx,(pre!=0)*pre+1,k); } } } } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); ll n,m,i; cin>>n>>m; jc[0]=jc_inv[0]=1; for(i=1;i<=n;i++) { inv[i]=qpow(i,p-2,p); jc[i]=jc[i-1]*i%p; jc_inv[i]=jc_inv[i-1]*inv[i]%p; } if(n==m) { for(i=1;i<=n-1;i++) { cout<<0<<" "; } cout<<1<<endl; } else { dfs(1,n,n-m,m,0,0,m/2); for(i=1;i<=m/2;i++) { cout<<f[i]<<" "; } for(i=m/2+1;i<=m;i++) { cout<<(2*C(n-i-1,m-i,p)%p+C(n-i-2,m-i,p)*(n-i-1)%p)%p<<" "; } } fclose(stdin); fclose(stdout); return 0; }
-
正解
- 发现统计 恰好为 \(k\) 的方案数不太好做,考虑统计 至多为 \(k\) 的方案数,然后临项相减即可得到最终结果。统计 至少为 \(k\) 的方案数同理。
- 观察到 \(n-m\) 个 \(0\) 会将 \(1\) 分割成 \(n-m+1\) 段(每段可以为空),等价于查询 \(\sum\limits_{i=1}^{n-m+1}x_{i}=m(x_{i} \le k)\) 的非负整数解的个数。
- 考虑组合意义,将 \(m\) 个小球放到 \(n-m+1\) 的盒子里,每个盒子限重为 \(k\) 。
- 接着进行二项式反演,设 \(f_{i}\) 表示恰好 \(i\) 个盒子超限的方案数, \(g_{i}\) 表示钦定/至少 \(i\) 个盒子超限的方案数。考虑计算 \(g_{i}\) 的组合意义,先选出 \(i\) 个盒子里各放 \(k+1\) 个小球使其超限,接着将剩下的 \(m-i(k+1)\) 个小球放到 \(n-m+1\) 个盒子里进行插板,有 \(g_{i}=\dbinom{n-m+1}{i}\dbinom{m-i(k+1)+n-m+1-1}{n-m+1-1}=\dbinom{n-m+1}{i}\dbinom{m-i(k+1)+n-m}{n-m}\) 。
- 最后有 \(f_{0}=\sum\limits_{i=0}^{n-m+1}(-1)^{i-0}\dbinom{i}{0}g_{i}=\sum\limits_{i=0}^{n-m+1}(-1)^{i}g_{i}\) 。
- 不难发现 \(i \le \min(n-m+1,\frac{m}{k+1})\) ,所以时间复杂度为调和级数的 \(O(n \log n)\) 。
点击查看代码
const ll p=1000000007; ll inv[400010],jc[400010],jc_inv[400010],sum[400010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } a=a*a%p; b>>=1; } return ans; } ll C(ll n,ll m,ll p) { return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); ll n,m,i,k; cin>>n>>m; jc[0]=jc_inv[0]=1; for(i=1;i<=n+m;i++) { inv[i]=qpow(i,p-2,p); jc_inv[i]=jc_inv[i-1]*inv[i]%p; jc[i]=jc[i-1]*i%p; } for(k=1;k<=m;k++) { for(i=0;i<=min(n-m+1,m/(k+1));i++) { if(i%2==0) { sum[k]=(sum[k]+C(n-m+1,i,p)*C(m-i*(k+1)+n-m,n-m,p)%p)%p; } else { sum[k]=(sum[k]-C(n-m+1,i,p)*C(m-i*(k+1)+n-m,n-m,p)%p+p)%p; } } } for(i=m;i>=1;i--) { sum[i]=(sum[i]-sum[i-1]+p)%p; } for(i=1;i<=m;i++) { cout<<sum[i]<<" "; } fclose(stdin); fclose(stdout); return 0; }
\(T3\) P296. 星空遗迹 \(20pts\)
-
弱化版: QOJ 8046. Rock-Paper-Scissors Pyramid | Gym104065M Rock-Paper-Scissors Pyramid
-
部分分
-
\(20 \%\) :模拟,时间复杂度为 \(O(qn^{2})\) 。
点击查看代码
char s[200010],tmp[200010]; char w(char x,char y) { if(x==y){return x;} if(x!='R'&&y!='R'){return 'S';} if(x!='S'&&y!='S'){return 'P';} if(x!='P'&&y!='P'){return 'R';} return 'A'; } void f(char s[],int len) { for(int i=1;i<=len-1;i++) { s[i]=w(s[i],s[i+1]); } } char ask(int l,int r) { for(int i=l,j=1;i<=r;i++,j++) { tmp[j]=s[i]; } for(int i=l,j=r-l+1;i<=r;i++,j--) { f(tmp,j); } return tmp[1]; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,q,pd,l,r,i; char c; cin>>n>>q>>(s+1); for(i=1;i<=q;i++) { cin>>pd; if(pd==1) { cin>>l>>c; s[l]=c; } else { cin>>l>>r; cout<<ask(l,r)<<endl; } } fclose(stdin); fclose(stdout); return 0; }
-
另外 \(40 \%\)
-
先是一些大力结论。
- 连续的相同元素的个数没有什么用,只保留一个元素参与运算。
- 如果一段连续的相同元素的左/右边存在能赢它们的字符,可直接删除这整段连续的相同元素。
-
考虑在从 \(l\) 扫到 \(r\) 的过程中维护一个栈不断插入字符,使得从栈底到栈顶每个元素都能赢在它上面的一个元素,最终的栈底元素即为答案所求,单次询问的时间复杂度为 \(O(n)\) 。
- 本质上是构造连续的形如
RPSRPS...
的字符串,或许用胜负关系表示括号序更好理解。
点击查看代码
deque<char>q; char s[200010]; char w(char x,char y) { if(x==y){return x;} if(x!='R'&&y!='R'){return 'S';} if(x!='S'&&y!='S'){return 'P';} if(x!='P'&&y!='P'){return 'R';} return 'A'; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,m,pd,l,r,i,j; char c; cin>>n>>m>>(s+1); for(j=1;j<=m;j++) { cin>>pd; if(pd==1) { cin>>l>>c; s[l]=c; } else { cin>>l>>r; q.clear(); for(i=l;i<=r;i++) { while(q.empty()==0&&w(q.back(),s[i])==s[i]) { q.pop_back(); } q.push_back(s[i]); } cout<<q.front()<<endl; } } fclose(stdin); fclose(stdout); return 0; }
- 本质上是构造连续的形如
-
-
-
正解
- 考虑数据结构优化上面的内容。
- 定义定义域为 \(\{ R,P,S \}\) 下的 \(a>b\) 表示 \(a\) 能赢 \(b\) , \(a=b\) 表示 \(a,b\) 平局, \(a<b\) 表示 \(b\) 能赢 \(a\) 。
- 设 \(f_{i}\) 表示从 \(1\) 开始,在插入 \(i\) 后的栈的大小,有 \(f_{i}=\begin{cases}f_{i-1}+1 & s_{i-1}>s_{i} \\ f_{i} & s_{i-1}=s_{i} \\ \max(f_{i}-1,1) & s_{i-1}<s_{i} \end{cases}\) ,边界为 \(f_{1}=1\) 。
- 可以证明,对于全局查询,答案显然来自最后一个 \(f_{i}=1\) 的位置。
- 考虑去掉转移过程中的和 \(1\) 取 \(\max\) ,则 \([l,r]\) 中最后一个最小的 \(f_{i}\) 即为所求(尽管加上和 \(1\) 取 \(\max\) 后可能也不等于 \(1\) ,但代码写出来是对的,有严格证明了跟我说)。
- 是否是最后一个最小的 \(f_{i}\) 并不重要,因为其 \(s_{i}\) 都是一样的,中途进行了相互抵消,感性理解吧。
- 对 \(\{ s \}\) 的单点修改可以转化为对胜负关系前缀和后(即维护栈后)的后缀修改和区间查询最小值。
点击查看代码
int f[200010],g[200010]; char s[200010]; int w(char x,char y) { if(x==y){return 0;} if(x!='R'&&y!='R'){return ('S'==x)?1:-1;} if(x!='S'&&y!='S'){return ('P'==x)?1:-1;} if(x!='P'&&y!='P'){return ('R'==x)?1:-1;} return 2; } struct SMT { struct SegmentTree { int l,r,minn,pos,lazy; }tree[800010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { if(tree[lson(rt)].minn>=tree[rson(rt)].minn) { tree[rt].minn=tree[rson(rt)].minn; tree[rt].pos=tree[rson(rt)].pos; } else { tree[rt].minn=tree[lson(rt)].minn; tree[rt].pos=tree[lson(rt)].pos; } } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; if(l==r) { tree[rt].minn=f[l]; tree[rt].pos=l; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(int rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[lson(rt)].minn+=tree[rt].lazy; tree[rson(rt)].minn+=tree[rt].lazy; tree[rt].lazy=0; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lazy+=val; tree[rt].minn+=val; return; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } pair<int,int> query(int rt,int x,int y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return make_pair(tree[rt].minn,tree[rt].pos); } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; pair<int,int>p,q; if(y<=mid) { return query(lson(rt),x,y); } else { if(x>mid) { return query(rson(rt),x,y); } else { p=query(lson(rt),x,y); q=query(rson(rt),x,y); return (p.first>=q.first)?q:p; } } } }T; int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,m,pd,l,r,i; char c; cin>>n>>m>>(s+1); f[1]=1; for(i=2;i<=n;i++) { g[i]=w(s[i-1],s[i]); f[i]=f[i-1]+g[i]; } T.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { cin>>l>>c; if(l-1>=1) { T.update(1,l,n,-g[l]); s[l]=c; g[l]=w(s[l-1],s[l]); T.update(1,l,n,g[l]); } else { s[l]=c; } if(l+1<=n) { T.update(1,l+1,n,-g[l+1]); g[l+1]=w(s[l],s[l+1]); T.update(1,l+1,n,g[l+1]); } } else { cin>>l>>r; cout<<s[T.query(1,l,r).second]<<endl; } } fclose(stdin); fclose(stdout); return 0; }
\(T4\) P297. 纽带 \(10pts\)
- 弱化版: CF1874F Jellyfish and OEIS
- 原题多了个条件 \(2\) 的限制。
- 部分分
-
\(10 \%\) :模拟。时间复杂度为 \(O(n! \times n^{3})\) 。
点击查看代码
const int mod=1000000007; int m[50],p[50],cnt[50],f[3000]; bool cmp(int l,int r) { memset(cnt,0,sizeof(cnt)); for(int i=l;i<=r;i++) { cnt[p[i]]++; } for(int i=l;i<=r;i++) { if(cnt[i]==0) { return false; } } return true; } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,sum,flag,l,r,i; cin>>n; for(i=1;i<=n;i++) { cin>>m[i]; p[i]=i; } do { sum=0; flag=1; for(l=1;l<=n;l++) { for(r=l;r<=m[l];r++) { flag&=(cmp(l,r)==false); if(flag==0) { break; } } if(flag==0) { break; } } if(flag==1) { for(l=1;l<=n;l++) { for(r=l;r<=n;r++) { sum+=(cmp(l,r)==true); } } f[sum]=(f[sum]+1)%mod; } }while(next_permutation(p+1,p+1+n)); for(i=1;i<=n*(n+1)/2;i++) { cout<<f[i]<<" "; } fclose(stdin); fclose(stdout); return 0; }
-
\(20 \%\) :模拟,不难发现若 \([p_{l},p_{l+1}, \dots ,p_{r}]\) 是 $[l,l+1, \dots ,r] $ 的一个排列,则有 \(\begin{cases} \max\limits_{i=l}^{r}\{ p_{i} \}=r \\ \min\limits_{i=l}^{r}\{ p_{i} \}=l \end{cases}\)。时间复杂度为 \(O(n! \times n^{2})\) 。
点击查看代码
const int mod=1000000007; int m[50],p[50],f[3000]; int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n,sum,flag,maxx,minn,l,r,i; cin>>n; for(i=1;i<=n;i++) { cin>>m[i]; p[i]=i; } do { sum=0; flag=1; for(l=1;l<=n;l++) { maxx=0; minn=0x7f7f7f7f; for(r=l;r<=m[l];r++) { maxx=max(maxx,p[r]); minn=min(minn,p[r]); flag&=(maxx!=r||minn!=l); if(flag==0) { break; } } if(flag==0) { break; } } if(flag==1) { for(l=1;l<=n;l++) { maxx=0; minn=0x7f7f7f7f; for(r=l;r<=n;r++) { maxx=max(maxx,p[r]); minn=min(minn,p[r]); sum+=(maxx==r&&minn==l); } } f[sum]=(f[sum]+1)%mod; } }while(next_permutation(p+1,p+1+n)); for(i=1;i<=n*(n+1)/2;i++) { cout<<f[i]<<" "; } fclose(stdin); fclose(stdout); return 0; }
-
- 正解
-
析合树上跑 \(DP\) ,直接挂官方题解了。
-
总结
- \(T2\) 数组开小挂了 \(5pts\) 。
- \(T4\) 没有意识到自己判断是否是排列和枚举右端点可以继承,所以写的是 \(O(n! \times n^{3})\) ,挂了 \(15pts\) 。
后记
-
\(T1\) 的数据范围出锅了,话说 \(miaomiao\) 还会自己造数据?
-
\(T4\) 富有启发性意义的题目背景。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18446609,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。