2024初三集训模拟测试3
2024初三集训模拟测试3
\(T1\) 计蒜客 T3726 排序 \(0pts\)
-
考虑先将 \(a\) 数组进行升序排列。然后题意转化为了把 \(1 \sim 2n\) 平均分成 \(2\) 组,使得每组乘积之和最小;把 \(2n+1 \sim 4n\) 平均分成 \(2\) 组,使得每组乘积之和最大。
-
对于 \(x_{1},x_{2},x_{3},x_{4}\) 若存在 \(x_{1}<x_{2}<x_{3}<x_{4}\) 则有 \(x_{1}x_{2}+x_{3}x_{4}>x_{1}x_{3}+x_{2}x_{4}>x_{1}x_{4}+x_{2}x_{3}\) ,若加上等于号则有 \(x_{1}x_{2}+x_{3}x_{4} \ge x_{1}x_{3}+x_{2}x_{4} \ge x_{1}x_{4}+x_{2}x_{3}\) 。
- 其实就是 普及模拟2 T2 内积 的结论的推广。
-
故最终 \(\sum\limits_{i=2n+1}^{4n}[i \bmod 2=1] \times x_{i}x_{i+1}-\sum\limits_{i=1}^{n}x_{i}x_{2n-i+1}\) 即为所求。
点击查看代码
ll a[500001]; int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=4*n;i++) { cin>>a[i]; } sort(a+1,a+1+4*n); for(i=1;i<=n;i++) { ans-=a[i]*a[2*n-i+1]; } for(i=2*n+1;i<=4*n;i+=2) { ans+=a[i]*a[i+1]; } cout<<ans<<endl; return 0; }
\(T2\) 计蒜客 T3727 牛吃草 \(0pts\)
-
部分分
- \(90pts\)
-
为使最小覆盖大小最大,考虑二分答案,设当前二分出来的答案为 \(mid\) 。
-
设 \(f_{i}\) 表示对 \([1,i]\) 完成最小覆盖大小 \(\ge mid\) 的覆盖后的最大总覆盖大小,状态转移方程为 \(\begin{aligned} f_{i}=\max(f_{i-1},\max\limits_{j=i-w_{i}}^{i-mid} \{ f_{j}+(i-j) \})=\max(f_{i-1}, \max\limits_{j=i-w[i]}^{i-mid} \{ f_{j}-j \}+i) \end{aligned}\) 。暴力进行转移即可。
点击查看代码
int w[600000],f[600000]; bool check(int mid,int n,int m) { int i,j; memset(f,0,sizeof(f)); for(i=1;i<=n;i++) { f[i]=f[i-1]; if(i-w[i]>=0) { for(j=i-w[i];j<=i-mid;j++) { f[i]=max(f[i],f[j]+(i-j)); } } } return f[n]>=m; } int main() { int n,m,s,l=1,r,mid,i; cin>>n; for(i=1;i<=n;i++) { cin>>w[i]; } cin>>s; r=m=ceil(1.0*n*s/100); while(l<=r) { mid=(l+r)/2; if(check(mid,n,m)==true) { l=mid+1; } else { r=mid-1; } } cout<<r<<endl; return 0; }
-
- \(90pts\)
-
正解
- 由于 \(w_{i-1} \ge w_{i}-1\) ,故 \((i-1)-w_{i-1} \le i-w_{i}\) ,故当 \(i\) 增大时,有 \(j\) 的上界增加了 \(1\) ,下界减少了 \(w_{i-1}-(w_{i}-1)\) ,使用单调队列维护即可。
点击查看代码
int w[600000],f[600000]; bool check(int mid,int n,int m) { deque<int>q; for(int i=1;i<=n;i++) { f[i]=f[i-1]; if(i-mid>=0) { while(q.empty()==0&&f[q.back()]-q.back()<=f[i-mid]-(i-mid)) { q.pop_back(); } q.push_back(i-mid); while(q.empty()==0&&q.front()<=i-w[i]-1) { q.pop_front(); } if(q.empty()==0) { f[i]=max(f[i],f[q.front()]-q.front()+i); } } } return f[n]>=m; } int main() { int n,m,s,l=1,r,mid,i; cin>>n; for(i=1;i<=n;i++) { cin>>w[i]; } cin>>s; r=n; m=ceil(1.0*n*s/100); check(1,n,m); while(l<=r) { mid=(l+r)/2; if(check(mid,n,m)==true) { l=mid+1; } else { r=mid-1; } } cout<<r<<endl; return 0; }
\(T3\) 计蒜客 T3728 树上的宝藏 \(0pts\)
-
部分分
- \(60pts\) :设 \(f_{x,0},f_{x,1}\) 分别表示以 \(x\) 为根节点的子树内,不选和选 \(x\) 的方案数。状态转移方程为 \(\begin{cases} f_{x,0}=\prod\limits_{y \in Son(x)}^{}(f_{y,0}+f_{y,1}) \\ f_{x,1}=\prod\limits_{y \in Son(x)}^{}f_{y,0} \end{cases}\) 。对于一条边 \((x,y)\) ,砍断这条边后,进行树形 \(DP\) ,此时 \(f_{x,0}f_{y,1}+f_{x,1}f_{y,0}+f_{x,1}f_{y,1}\) 即为所求。
-
正解
- 发现砍断操作可以用换根 \(DP\) 优化。
- 钦定 \(1\) 为根节点。
- 第一遍 \(DFS\) 时,处理出 \(f_{x,0},f_{x,1}\) 。
- 第二遍 \(DFS\) 时,进行换根。设 \(g_{x,0},g_{x,1}\) 表示以 \(x\) 为整棵树的根节点时,不选和选 \(x\) 的方案数,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x(x \ne 1)\) 的过程中,以 \(x\) 为根的子树内的节点对答案的贡献不变,要消除以 \(x\) 为根的子树内的节点对以 \(fa_{x}\) 为根的子树的影响,故状态转移方程为 \(\begin{cases} g_{x,0}=f_{x,0} \times (\frac{g_{fa_{x},0}}{f_{x,0}+f_{x,1}}+\frac{g_{fa_{x},1}}{f_{x,0}}) \\ g_{x,1}=f_{x,1} \times \frac{g_{fa_{x},0}}{f_{x,0}+f_{x,1}} \end{cases}\) 。
- 对于一条边 \((x,y)\) ,进行分类讨论。
- 若不选 \(x\) ,选 \(y\) ,则有 \(f_{y,1} \times \frac{g_{x,0}}{f_{y,0}+f_{y,1}}\) 即为所求。
- 若选 \(x\) ,不选 \(y\) ,则有 \(f_{y,0} \times \frac{g_{x,1}}{f_{y,0}}=g_{x,1}\) 即为所求。
- 若选 \(x,y\) ,则有 \(f_{y,1} \times \frac{g_{x,1}}{f_{y,0}}\) 即为所求。
点击查看代码
const ll p=998244353; struct node { ll nxt,to; }e[700000]; ll head[700000],dep[700000],u[700000],v[700000],g[700000][2],f[700000][2],cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b>0) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs(ll x,ll fa) { f[x][0]=f[x][1]=1; dep[x]=dep[fa]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x][0]=f[x][0]*((f[e[i].to][0]+f[e[i].to][1])%p)%p; f[x][1]=f[x][1]*f[e[i].to][0]%p; } } } void reroot(ll x,ll fa) { if(x!=1) { ll f0=g[fa][0]*qpow((f[x][0]+f[x][1])%p,p-2,p)%p,f1=g[fa][1]*qpow(f[x][0],p-2,p)%p; g[x][0]=f[x][0]*((f0+f1)%p)%p; g[x][1]=f[x][1]*f0%p; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { reroot(e[i].to,x); } } } int main() { ll n,sum1,sum2,sum3,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u[i]>>v[i]; add(u[i],v[i]); add(v[i],u[i]); } dfs(1,0); g[1][0]=f[1][0]; g[1][1]=f[1][1]; reroot(1,0); for(i=1;i<=n-1;i++) { if(dep[v[i]]<dep[u[i]]) { swap(u[i],v[i]); } sum1=((f[v[i]][1]*g[u[i]][0])%p)*qpow((f[v[i]][0]+f[v[i]][1])%p,p-2,p)%p; sum2=g[u[i]][1]; sum3=((f[v[i]][1]*g[u[i]][1])%p)*qpow(f[v[i]][0]%p,p-2,p)%p; cout<<(((sum1+sum2)%p)+sum3)%p<<endl; } return 0; }
- 发现砍断操作可以用换根 \(DP\) 优化。
\(T4\) 计蒜客 T3729 MEX \(0pts\)
-
Special Judge
: luogu P4137 Rmq Problem / mex -
部分分
-
\(10pts\)
-
对于每个区间枚举左右端点,进行暴力求解。
点击查看代码
ll a[5000000],vis[5000000],ans[5000000]; int main() { ll n,m,p,q,l,r,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } for(l=1;l<=n;l++) { for(r=l;r<=n;r++) { for(i=l;i<=r;i++) { vis[a[i]]=1; } for(i=0;i<=m;i++) { if(vis[i]==0) { ans[i]++; break; } } for(i=0;i<=m;i++) { vis[i]=0; } } } cin>>p>>q; for(i=p;i<=q;i++) { cout<<ans[i]<<" "; } return 0; }
-
-
\(20pts\)
-
使用莫队进行统计答案。
点击查看代码
int a[3000010],cnt[3000010],lscnt[3000010],ans[3000010],L[3000010],R[3000010],pos[3000010],klen,ksum; struct ask { int l,r,id; }q[3000010]; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?(a.r>b.r):(a.l<b.l); } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void add(int cnt[],int x) { cnt[a[x]]++; } void del(int cnt[],int x,int &sum) { cnt[a[x]]--; sum=(cnt[a[x]]==0)?min(sum,a[x]):sum; } int main() { int n,mm,m=0,x,y,l=1,r,anss=0,sum=0,tmp=0,lastblock=0,i,j; cin>>n>>mm; r=n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { for(j=i;j<=n;j++) { m++; q[m].l=i; q[m].r=j; q[m].id=m; } } init(n,m); sort(q+1,q+1+m,q_cmp); for(i=1;i<=n;i++) { add(cnt,i); } while(cnt[anss]>=1) { anss++; } for(i=1;i<=m;i++) { if(pos[q[i].l]==pos[q[i].r]) { sum=0; for(j=q[i].l;j<=q[i].r;j++) { add(lscnt,j); } while(lscnt[sum]>=1) { sum++; } for(j=q[i].l;j<=q[i].r;j++) { lscnt[a[j]]--; } } else { if(lastblock!=pos[q[i].l]) { while(r<n) { r++; add(cnt,r); } while(l<L[pos[q[i].l]]) { del(cnt,l,anss); l++; } tmp=anss; lastblock=pos[q[i].l]; } while(r>q[i].r) { del(cnt,r,tmp); r--; } sum=tmp; while(l<q[i].l) { del(cnt,l,sum); l++; } while(l>L[pos[q[i].l]]) { l--; add(cnt,l); } } ans[sum]++; } cin>>x>>y; for(i=x;i<=y;i++) { cout<<ans[i]<<" "; } return 0; }
-
-
\(30pts\)
点击查看 APJifengc 写的题解
-
-
正解
点击查看 APJifengc 写的题解
const int MAXN = 1e6 + 5; int n, m; int arr[MAXN]; int last[MAXN]; int nxt[MAXN]; long long answer[MAXN]; struct SegmentTree { public: int min, num; long long sum; class LazyTag { public: int cover, tim; long long add; inline LazyTag() { cover = -1; add = 0; tim = 0; } inline LazyTag(const int cover): cover(cover) { if (cover == -1) { tim = 1; } } } tag; } sgt[MAXN << 2]; inline void PushUp(const int now) { sgt[now].min = min(sgt[now << 1].min, sgt[now << 1 | 1].min); } void Build(const int now = 1, const int left = 0, const int right = m) { if (left == right) { sgt[now].num = sgt[now].min = last[left]; return; } Build(now << 1, left, (left + right) >> 1); Build(now << 1 | 1, ((left + right) >> 1) + 1, right); PushUp(now); } inline void Down(const SegmentTree::LazyTag tag, const int now, const int left, const int right) { sgt[now].sum += 1ll * sgt[now].num * tag.tim + tag.add; if (~tag.cover) { if (~sgt[now].tag.cover) { sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim + tag.add; } else { sgt[now].tag.tim += tag.tim; sgt[now].tag.add = tag.add; } sgt[now].num = sgt[now].min = tag.cover; sgt[now].tag.cover = tag.cover; } else { if (~sgt[now].tag.cover) { sgt[now].tag.add += 1ll * sgt[now].tag.cover * tag.tim; } else { sgt[now].tag.tim += tag.tim; } } } inline void PushDown(const int now, const int left, const int right) { Down(sgt[now].tag, now << 1, left, (left + right) >> 1); Down(sgt[now].tag, now << 1 | 1, ((left + right) >> 1) + 1, right); sgt[now].tag = SegmentTree::LazyTag(); } void Updata(const int now_left, const int now_right, const int cover, const int now = 1, const int left = 0, const int right = m) { if (now_right < left || right < now_left) { return; } if (now_left <= left && right <= now_right) { Down(SegmentTree::LazyTag(cover), now, left, right); return; } PushDown(now, left, right); Updata(now_left, now_right, cover, now << 1, left, (left + right) >> 1); Updata(now_left, now_right, cover, now << 1 | 1, ((left + right) >> 1) + 1, right); PushUp(now); } inline void Time() { Down(SegmentTree::LazyTag(-1), 1, 1, m); } int Find(const int now_left, const int now_right, const int val, const int now = 1, const int left = 0, const int right = m) { if (now_right < left || right < now_left || val <= sgt[now].min) { return -1; } if (left == right && now_left <= left && right <= now_right) { return left; } int result(Find(now_left, now_right, val, now << 1 | 1, ((left + right) >> 1) + 1, right)); return ~result ? result : Find(now_left, now_right, val, now << 1, left, (left + right) >> 1); } void GetAnswer(const int now = 1, const int left = 0, const int right = m) { if (left == right) { answer[left] = sgt[now].sum; return; } PushDown(now, left, right); GetAnswer(now << 1, left, (left + right) >> 1); GetAnswer(now << 1 | 1, ((left + right) >> 1) + 1, right); } int main() { scanf("%d%d", &n, &m); int last0 = 0; long long answer0 = 0; for (int i = 1; i <= n; i++) { scanf("%d", &arr[i]); if (arr[i] == 0) { answer0 += (i - last0 - 1ll) * (i - last0) >> 1; last0 = i; } } answer0 += (n - last0) * (n + 1ll - last0) >> 1; for (int i = 0; i <= m; i++) { last[i] = n + 1; } for (int i = n; i >= 1; i--) { nxt[i] = last[arr[i]]; last[arr[i]] = i; } for (int i = 1; i <= m - 1; i++) { last[i] = max(last[i], last[i - 1]); } Build(); for (int i = 1; i <= n; i++) { Time(); int f = Find(arr[i], m, nxt[i]); if (~f) { Updata(arr[i], f, nxt[i]); } } GetAnswer(); int l, r; scanf("%d%d", &l, &r); if (!l) { printf("%lld ", answer0); l = 1; } for (int i = l; i <= r; i++) { printf("%lld ", answer[i] - answer[i - 1]); } return 0; }
总结
- \(T1\) 场上结论没有猜出来,导致心态炸了。
- \(T2\) 场上只看出来了二分答案,没有再接着去想,而且貌似还读错题了。
- \(T3\) 场上读假题了。
- \(T4\) 场上部分分没打,亏了。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18026551,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。