暑假集训CSP提高模拟26
暑假集训CSP提高模拟26
\(T1'\) 前缀 \(0pts\)
- 原题: [ABC366D] Cuboid Sum Query
- 三维前缀和板子。
-
由容斥原理,容易有 \(s_{i,j,k}=s_{i,j,k-1}+s_{i,j-1,k}+s_{i-1,j,k}-s_{i-1,j-1,k}-s_{i-1,j,k-1}-s_{i,j-1,k-1}+s_{i-1,j-1,k-1}+a_{i,j,k}\) 。
-
类似地,询问时 \(s_{r_{x},r_{y},r_{z}}-s_{r_{x},r_{y},l_{z}-1}-s_{r_{x},l_{y}-1,r_{z}}-s_{l_{x}-1,r_{y},r_{z}}+s_{l_{x}-1,l_{y}-1,r_{z}}+s_{l_{x}-1,r_{y},l_{z}-1}+s_{r_{x},l_{y}-1,l_{z}-1}-s_{l_{x}-1,l_{y}-1,l_{z}-1}\) 即为所求。
点击查看代码
int a[110][110][110],s[110][110][110]; int main() { int n,m,lx,ly,lz,rx,ry,rz,i,j,k; cin>>n; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { for(k=1;k<=n;k++) { cin>>a[i][j][k]; s[i][j][k]=s[i][j][k-1]+s[i][j-1][k]+s[i-1][j][k]-s[i-1][j-1][k]-s[i-1][j][k-1]-s[i][j-1][k-1]+s[i-1][j-1][k-1]+a[i][j][k]; } } } cin>>m; for(i=1;i<=m;i++) { cin>>lx>>rx>>ly>>ry>>lz>>rz; cout<<s[rx][ry][rz]-s[rx][ry][lz-1]-s[rx][ly-1][rz]-s[lx-1][ry][rz]+s[lx-1][ly-1][rz]+s[lx-1][ry][lz-1]+s[rx][ly-1][lz-1]-s[lx-1][ly-1][lz-1]<<endl; } return 0; }
-
- 枚举一维,另外的二维前缀和维护。
\(T2'\) P270.树 \(100pts\)
\(T3'\) P271.背包 \(100pts\)
- 原题: LibreOJ 6089. 小 Y 的背包计数问题
- 详见 初三奥赛模拟测试2 T4 义 。
\(T4'\) P272.序列 \(0pts\)
-
第一问在第二问的基础上删除了 \(\gcd(a_{1},a_{2},a_{3}, \dots ,a_{n})=1\) 的限制。
-
看到 \(\gcd\) 考虑莫反。
-
推下式子,有 \(\begin{aligned} &\sum\limits_{a_{1}=l_{1}}^{r_{1}}\sum\limits_{a_{2}=l_{2}}^{r_{2}} \dots \sum\limits_{a_{n}=l_{n}}^{r_{n}}[\sum\limits_{i=1}^{n}a_{i} \le m \land \gcd(a_{1},a_{2},a_{3}, \dots ,a_{n})=1] \\ &=\sum\limits_{d=1}^{m}\mu(d)\sum\limits_{a_{1}=l_{1}}^{r_{1}}[d|a_{1}]\sum\limits_{a_{2}=l_{2}}^{r_{2}}[d|a_{2}] \dots \sum\limits_{a_{n}=l_{n}}^{r_{n}}[d|a_{n}] \times [\sum\limits_{i=1}^{n}a_{i} \le m] \\ &=\sum\limits_{d=1}^{m}\mu(d)\sum\limits_{a_{1}=\left\lceil \frac{l_{1}}{d} \right\rceil}^{\left\lfloor \frac{r_{1}}{d} \right\rfloor}\sum\limits_{a_{2}=\left\lceil \frac{l_{2}}{d} \right\rceil}^{\left\lfloor \frac{r_{2}}{d} \right\rfloor}\dots \sum\limits_{a_{n}=\left\lceil \frac{l_{n}}{d} \right\rceil}^{\left\lfloor \frac{r_{n}}{d} \right\rfloor}[\sum\limits_{i=1}^{n}a_{i} \le \left\lfloor \frac{m}{d} \right\rfloor] \end{aligned}\) 。
-
提出后半部分,设 \(f_{i,j}\) 表示前 \(i\) 个数中总和为 \(j\) 的方案数,状态转移方程为 \(f_{i,j}=\sum\limits_{k=l_{i}}^{\min(j,r_{i})}f_{i-1,j-k}\) 。前缀和优化即可。
-
因为有调和级数所以时间复杂度是正确的。
点击查看代码
const ll p=998244353; ll f[2][100010],s[100010],l[100010],r[100010],nl[100010],nr[100010],prime[100010],vis[100010],miu[100010],len=0; void isprime(ll n) { memset(vis,0,sizeof(vis)); miu[1]=1; for(ll i=2;i<=n;i++) { if(vis[i]==0) { len++; prime[len]=i; miu[i]=-1; } for(ll j=1;j<=len&&i*prime[j]<=n;j++) { vis[i*prime[j]]=1; if(i%prime[j]==0) { miu[i*prime[j]]=0; break; } else { miu[i*prime[j]]=-miu[i]; } } } } ll ask(ll n,ll m) { f[0][0]=1; for(ll i=1;i<=m;i++) { f[0][i]=0; } for(ll i=1;i<=n;i++) { for(ll j=0;j<=m;j++) { s[j]=((j-1>=0?s[j-1]:0)+f[(i-1)&1][j])%p; f[i&1][j]=0; } for(ll j=nl[i];j<=m;j++) { f[i&1][j]=(s[j-nl[i]]-((j-nr[i]-1>=0)?s[j-nr[i]-1]:0)+p)%p; } } ll ans=0; for(ll i=0;i<=m;i++) { ans=(ans+f[n&1][i])%p; } return ans; } int main() { ll n,m,ans=0,i,j; scanf("%lld%lld",&n,&m); isprime(m); for(i=1;i<=n;i++) { scanf("%lld%lld",&l[i],&r[i]); } for(i=1;i<=m;i++) { if(miu[i]!=0) { for(j=1;j<=n;j++) { nl[j]=ceil(1.0*l[j]/i); nr[j]=r[j]/i; } ans=(ans+miu[i]*ask(n,m/i)+p)%p; } } printf("%lld\n",ans); return 0; }
\(T1\) P273. 博弈 \(0pts\)
-
设 \(cnt_{i}\) 表示路径 \(u \to v\) 的路径上 \(i\) 的出现次数。
-
由 CF1990A Submission Bait ,不难发现对于点对 \((u,v)\)
Crying
必胜当且仅当 \(\exists w \in [0,10^{9}],cnt_{i} \bmod 2=1\) 。 -
部分分
-
\(30pts\) :枚举所有点对判断是否有解,每次钦定一个作为起点的单次询问时间复杂度为 \(O(n^{2})\) 。
点击查看代码
struct node { ll nxt,to,w; }e[1000010]; ll head[1000010],u[1000010],v[1000010],w[1000010],b[1000010],sum[1000010],num[2],cnt=0,ans; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll fa,ll rt,ll w) { if(x!=rt) { num[sum[w]%2]--; sum[w]++; num[sum[w]%2]++; ans+=(num[1]>=1); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,rt,e[i].w); } } if(x!=rt) { num[sum[w]%2]--; sum[w]--; num[sum[w]%2]++; } } int main() { int t,n,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; ans=cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); for(i=1;i<=n-1;i++) { cin>>u[i]>>v[i]>>w[i]; b[i]=w[i]; } sort(b+1,b+n); b[0]=unique(b+1,b+n)-(b+1); for(i=1;i<=n-1;i++) { w[i]=lower_bound(b+1,b+n,w[i])-b; add(u[i],v[i],w[i]); add(v[i],u[i],w[i]); } for(i=1;i<=n;i++) { dfs(i,0,i,0); } cout<<ans/2<<endl; } return 0; }
-
-
正解
-
发现和哈希在本题中无法合适地处理奇偶性,考虑异或哈希。
-
具体地,给边权随机赋一个 \([0,2^{64})\) 范围内的值 \(H_{w}\) ,一条路径上边权的集合的哈希值就是其中元素的 \(H\) 值的异或和。
rand
rand
返回一个[0,RAND_MAX]
的随机整数。在 \(Linux\) 下RAND_MAX
等于 \(2^{31}-1\) ,达到了unsigned int
类型的取值范围;但Windows
下RAND_MAX
等于 \(2^{15}-1\) ,仅达到了short
的上界。- 需要调用
#include<random>
。
mt19937
mt19937
作用和随机数范围同rand()
,均为unsigned int
类型的取值范围,但随机数质量和速度均比rand()
优。- 需要调用
#include<random>
。mt19937_64
随机数范围扩大到了unsigned long long
类型的取值范围。
- 使用
mt19937
时需先定义一个随机数生成器,例如mt19937 rng(seed)
,其中随机种子seed
若不填则为默认随机种子。需要生成随机数时直接调用rng()
即可。
random_device
- 梅森旋转器
random_device
是一个基于硬件的均匀分布随机数生成器,在 熵池耗尽前 效率较高,耗尽后性能急剧下降,常用作mt19937
等伪随机数生成器的种子来源。例如mt19937 rng(random_device{}());
。 - 需要调用
#include<random>
。
- 梅森旋转器
-
另外一种异或哈希的方法。
-
基于异或的性质,树上差分比较容易,维护树上前缀和后桶维护个数。
点击查看代码
struct node { ull nxt,to,w; }e[1000010]; ull head[1000010],sum[1000010],cnt=0,ans; mt19937_64 rng(random_device{}()); map<ull,ull>f,g; map<ull,ull>::iterator it; void add(ull u,ull v,ull w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ull x,ull fa,ull w) { sum[x]=sum[fa]^w; ans-=g[sum[x]]; g[sum[x]]++; for(ull i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,e[i].w); } } } int main() { ull t,n,u,v,w,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; ans=n*(n-1)/2; cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); g.clear(); for(i=1;i<=n-1;i++) { cin>>u>>v>>w; if(f.find(w)==f.end()) { f[w]=rng(); } add(u,v,f[w]); add(v,u,f[w]); } dfs(1,0,0); cout<<ans<<endl; } return 0; }
-
\(T2\) P274. 跳跃 \(20pts\)
-
部分分
-
\(20pts\) :爆搜。
点击查看代码
ll a[1010],sum[1010],ans; void dfs(ll pos,ll x,ll num,ll n,ll k) { ans=max(ans,num); if(pos==k+1) { return; } else { if(pos&1) { for(ll i=x;i<=n;i++) { if(num+sum[i]-sum[x-1]>=0) { dfs(pos+1,i,num+sum[i]-sum[x-1],n,k); } } } else { for(ll i=1;i<=x;i++) { if(num+sum[x]-sum[i-1]>=0) { dfs(pos+1,i,num+sum[x]-sum[i-1],n,k); } } } } } int main() { ll t,n,k,i,j; scanf("%lld",&t); for(j=1;j<=t;j++) { scanf("%lld%lld",&n,&k); ans=0; for(i=1;i<=n;i++) { scanf("%lld",&a[i]); sum[i]=sum[i-1]+a[i]; } dfs(1,1,0,n,k); printf("%lld\n",ans); } return 0; }
-
设 \(f_{i,j}\) 表示从 \(1\) 跳到 \(i\) 一共跳了 \(j\) 步的最大得分,状态转移方程为 \(f_{i,j}=\begin{cases} \max\limits_{k=1}^{i} \{ f_{k,j-1}+\sum\limits_{h=k}^{i}a_{h} \} & j \bmod 2=0 \\ \max\limits_{k=i}^{n} \{ f_{k,j-1}+\sum\limits_{h=i}^{k}a_{h} \} & j \bmod 2=1 \end{cases}\) ,维护前/后缀 \(\max\) 后时间复杂度为 \(O(nk)\) 。
-
-
正解
- 不难想到,我们会在一些连续段反复横跳,使得能跳到下一个更优的连续段继续反复横跳。
- 处理出 \(suf_{i}=\max\limits_{j=1}^{i}\{ \sum\limits_{k=j}^{i}a_{k} \}\) 。
- 设 \(f_{i}\) 表示从 \(1\) 跳到 \(i\) 后至多还能跳几次, \(g_{i}\) 表示在前者取到最优时从 \(1\) 跳到 \(i\) 时的最大得分。
- 转移时枚举上一步 \(j\) 由 \(suf_{j}\) 可以方便地计算出跳到 \(i\) 的最小次数。
- 最后统计答案时枚举所有右端点进行左右横跳即可。
点击查看代码
ll f[1010],g[1000],a[1010],sum[1010],suf[1010]; int main() { ll t,n,k,ans,minn,d,cnt,num,i,j,h; cin>>t; for(h=1;h<=t;h++) { cin>>n>>k; ans=minn=0; for(i=1;i<=n;i++) { cin>>a[i]; sum[i]=sum[i-1]+a[i]; suf[i]=sum[i]-minn; minn=min(minn,sum[i]); if(sum[i]>=0) { f[i]=k-1; g[i]=sum[i]; } else { f[i]=-1; g[i]=-0x7f7f7f7f; } } for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) { if(suf[j]>=suf[i]) { d=sum[j]-sum[i]; if(g[i]+d<0) { if(suf[i]>0) { cnt=ceil((0-(g[i]+d))/(2.0*suf[i])); num=g[i]+d+2*cnt*suf[i]; if(f[j]<f[i]-2*cnt) { f[j]=f[i]-2*cnt; g[j]=num; } else { if(f[j]==f[i]-2*cnt) { g[j]=max(g[j],num); } } } } else { if(f[j]<f[i]-(i==1)) { f[j]=f[i]-(i==1); g[j]=g[i]+d; } else { if(f[j]==f[i]-(i==1)) { g[j]=max(g[j],g[i]+d); } } } } } } for(i=1;i<=n;i++) { ans=max(ans,f[i]*suf[i]+g[i]); } cout<<ans<<endl; } return 0; }
\(T3\) P275. 大陆 \(0pts\)
-
原题: 2022牛客OI赛前集训营-提高组(第四场) C 大陆 | luogu P2325 [SCOI2005] 王室联邦
-
貌似是树分块的前置知识。
-
考虑对整棵子树进行 \(DFS\) ,处理每个节点时先将部分子节点分块,未被分块的子节点回溯到上一层。
-
具体地,枚举 \(x\) 的所有子节点 \(y\) ,递归处理完子树后,将未被分块的子节点加入待选集合 \(S\) 中,途中一旦 \(|S| \ge b\) 就将 \(S\) 作为一个新的块(块大小至多为 \(2b-2\) )并以 \(x\) 作为省会,并清空 \(S\) 。接着加入 \(x\) ,此时 \(|S|\) 至多为 \(b-1+1=b\) 。
-
最后把集合 \(S\) 中剩余的节点加入最后一个块,块大小至多为 \(2b-2+b+1=3b-1\) ,符合题意。
-
特判只有一个节点的情况。
点击查看代码
struct node { int nxt,to; }e[20010]; int head[20010],pos[20010],rt[20010],cnt=0,ksum=0; stack<int>s; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int fa,int b) { int tmp=s.size(); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,b); if(s.size()-tmp>=b) { ksum++; rt[ksum]=x; while(s.size()>tmp) { pos[s.top()]=ksum; s.pop(); } } } } s.push(x); } int main() { int n,b,u,v,i; cin>>n>>b; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0,b); if(ksum==0) { ksum++; rt[ksum]=1; } while(s.empty()==0) { pos[s.top()]=ksum; s.pop(); } cout<<ksum<<endl; for(i=1;i<=n;i++) { cout<<pos[i]<<" "; } cout<<endl; for(i=1;i<=ksum;i++) { cout<<rt[i]<<" "; } cout<<endl; return 0; }
\(T4\) P276. 排列 \(40pts\)
- 原题: 2022牛客OI赛前集训营-提高组(第四场) D 排列 | Baekjoon 17961.수열과 쿼리 35
- 部分分
-
\(40pts\) :暴力进行循环右移,权值树状数组重构整个序列,时间复杂度为 \(O(qn \log n)\) 。
点击查看代码
int a[120010],b[120010]; struct BIT { int c[120010]; int lowbit(int x) { return (x&(-x)); } void init(int n) { for(int i=1;i<=n;i++) { c[i]=0; } } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }le,ri; bool ask(int l,int r,int k,int n) { for(int i=l;i<=r;i++) { b[i]=a[i]; } for(int i=l,j=r-k+1;i<=l+k-1;i++,j++) { a[i]=b[j]; } for(int i=l+k,j=l;i<=r;i++,j++) { a[i]=b[j]; } le.init(n); ri.init(n); for(int i=1;i<=n;i++) { ri.add(n,a[i],1); } for(int i=1;i<=n;i++) { ri.add(n,a[i],-1); if(le.getsum(a[i]-1)>=1&&ri.getsum(n)-ri.getsum(a[i])>=1) { return true; } le.add(n,a[i],1); } return false; } int main() { int n,m,l,r,k,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } cin>>m; for(i=1;i<=m;i++) { cin>>l>>r>>k; if(ask(l,r,k,n)==true) { cout<<"YES"<<endl; } else { cout<<"NO"<<endl; } } return 0; }
-
- 正解
-
涉及到对 \(FHQ-Treap\) 的应用和理解,挂个官方题解就跑路了。
-
总结
- \(T1\) 因 \(VScode\) 终端编译延迟导致显示的是上份代码的信息,以为是这份代码的信息,然后没看出是 \(CE\) ,挂了 \(30pts\) 。
后记
-
组题人对 \(T1',T2',T3',T4'\) 的评价。
-
因 \(T2',T3'\) 我们都打过,所以 \(miaomiao\) 干脆把原来的 \(4\) 道题都换了。原 \(7:00\) 开始的比赛改成了 \(7:10\) 开始。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18371674,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。