2025多校冲刺省选模拟赛1
\(T1\) A. 切割蛋糕(cake) \(100pts\)
-
令 \(sum_{i}=sum_{i-1}+a_{i}\) 。
-
设
Alice
选择了 \(x\) 块蛋糕,总和为 \(s\) ,则限制条件为 \(\dfrac{s}{x} \ge \dfrac{sum_{n}-s}{n-x}\) ,移项得到 \(sn \ge sum_{n}x\) 。 -
设以 \(i\) 为起点,则需要保证 \(\begin{cases} \forall j \in [i,n], (sum_{j}-sum_{i}) \ge sum_{n}(j-i+1) \\ \forall j \in [1,i-2],(sum_{n}+sum_{j}-sum_{i-1}) \ge sum_{n}(n-i+j+1) \end{cases}\) ,移项后得到 \(sum_{j}n-sum_{n}j \ge sum_{i-1}n-sum_{n}i+sum_{n}\) ,分别前后缀取 \(\min\) 判断即可。
- 需要边界特判 \(i=1\) 时可选的只有 \([1,n-1]\) , \(i=2\) 时可选的只有 \([2,n]\) ,但数据貌似没卡。
点击查看代码
ll a[500010],sum[500010],flag[2][500010]; int main() { #define Isaac #ifdef Isaacfreopen("cake.in","r",stdin);freopen("cake.out","w",stdout); #endifll n,ans=-1,minn=0x7f7f7f7f7f7f7f7f,i;scanf("%lld",&n);for(i=1;i<=n;i++){scanf("%lld",&a[i]);sum[i]=sum[i-1]+a[i];}for(i=n;i>=1;i--){minn=min(minn,sum[i]*n-sum[n]*i);flag[0][i]=(minn>=sum[i-1]*n-sum[n]*i+sum[n]);}minn=0x7f7f7f7f7f7f7f7f;for(i=n-1;i>=1;i--){minn=min(minn,sum[i]*n-sum[n]*i);}flag[0][1]=flag[1][1]=(minn>=0);flag[1][2]=1;minn=0x7f7f7f7f7f7f7f7f;for(i=1;i<=n;i++){minn=min(minn,sum[i]*n-sum[n]*i);flag[1][i+2]=(minn>=sum[i+2-1]*n-sum[n]*(i+2)+sum[n]);}for(i=1;i<=n;i++){if(flag[0][i]==1&&flag[1][i]==1){ans=i;break;}}printf("%lld\n",ans);return 0; }
\(T2\) B. 游乐园(park) \(60pts\)
-
原题: luogu P3045 [USACO12FEB] Cow Coupons G
-
反悔贪心,因后面替换前面的 \(b_{j}\) 时可以保留 \(b_{j} \to a_{j}\) ,否则直接删掉 \(b_{j}\) ,故需要额外开两个小根堆存储当前未被使用 \(a,b\) 的贡献,需要懒惰删除。
点击查看代码
ll vis[200010]; pair<ll,ll>a[200010]; priority_queue<pair<ll,ll>,vector<pair<ll,ll> >,greater<pair<ll,ll> > >q1,q2,q3; int main() { #define Isaac #ifdef Isaacfreopen("park.in","r",stdin);freopen("park.out","w",stdout); #endifll n,k,t,sum=0,ans=0,i;cin>>n>>k>>t;for(i=1;i<=n;i++) {cin>>a[i].second>>a[i].first;}sort(a+1,a+1+n);for(i=1;i<=n;i++){if(q1.size()>=k||sum+a[i].first>t){q2.push(make_pair(a[i].first,i));q3.push(make_pair(a[i].second,i));}else{if(sum+a[i].first<=t){sum+=a[i].first;ans++;q1.push(make_pair(a[i].second-a[i].first,i));}}}for(i=q1.size()+1;i<=n;i++){while(q2.empty()==0&&vis[q2.top().second]==1){q2.pop();}while(q3.empty()==0&&vis[q3.top().second]==1){q3.pop();}if(q1.empty()==0){if(sum+min(q3.top().first,q2.top().first+q1.top().first)<=t){sum+=min(q3.top().first,q2.top().first+q1.top().first);ans++;}if(q3.top().first<q2.top().first+q1.top().first){vis[q3.top().second]=1;q3.pop();}else{vis[q2.top().second]=1;q1.pop();q1.push(make_pair(a[q2.top().second].second-q2.top().first,q2.top().second));q2.pop();}} else{if(sum+q3.top().first<=t){sum+=q3.top().first;ans++;}vis[q3.top().second]=1;q3.pop();}}cout<<ans<<endl;return 0; }
-
放两组 \(hack\) 数据。
点击查看数据 1
in: 10 1 8467 7058 57 4082 2989 1015 273 9569 2307 219 217 1953 433 8659 5494 4289 2050 3391 1301 9734 3199ans: 5
点击查看数据 2
in: 5 3 13 17 16 18 15 19 14 17 14 17 15ans: 0
\(T3\) C. 有根树(tree) \(10pts\)
-
部分分
-
\(10pts\) :枚举全排列。
点击查看代码
const ll mod=998244353; struct node {ll nxt,to; }e[500010]; ll head[500010],fa[500010],col[2][500010],p[500010],cnt=0; void add(ll u,ll v) {cnt++;e[cnt].nxt=head[u];e[cnt].to=v;head[u]=cnt; } void access(ll x,ll id) {for(ll i=head[x];i!=0;i=e[i].nxt){col[id][i]=0;}for(;x!=1;x=fa[x]){for(ll i=head[fa[x]];i!=0;i=e[i].nxt){col[id][i]=(e[i].to==x);}} } ll ask(ll n) {ll ans=0,flag;for(ll i=1;i<=n;i++){p[i]=i;}do{flag=1;fill(col[1]+1,col[1]+1+cnt,0);for(ll i=1;i<=n;i++){access(p[i],1);}for(ll i=1;i<=cnt&&flag==1;i++){flag&=(col[0][i]==col[1][i]);}ans=(ans+flag)%mod;}while(next_permutation(p+1,p+1+n));return ans; } int main() { #define Isaac #ifdef Isaacfreopen("tree.in","r",stdin);freopen("tree.out","w",stdout); #endifll n,m,x,i;scanf("%lld%lld",&n,&m);for(i=2;i<=n;i++){scanf("%lld",&fa[i]);add(fa[i],i);}for(i=1;i<=m;i++){scanf("%lld",&x);access(x,0);printf("%lld\n",ask(n));}return 0; }
-
-
正解
- 由实链剖分和
access
操作定义可知操作过程中每个点连向儿子的边中最多有一条实边。 - 定义一个节点也是一条实链。
- 观察到一条极长实链 \(x_{1} \to x_{2} \to \dots \to x_{k}\) 中链底节点 \(x_{k}\) 一定是链上节点中最后一次被操作的,其他节点的顺序随意。同时 \(x_{1}\) 比 \(fa_{x_{1}}\) 所在实链链底节点 \(p\) 操作靠前。对这棵树进行重构,非链底节点向所在链底连边,链底节点向父亲所在链底连边,限制条件转化为儿子节点比父亲节点先操作。
- 此时转化为了 树的拓扑序计数 ,易得递推式 \(\begin{aligned} f_{x} &=\dbinom{siz_{x}-1}{siz_{y_{1}},siz_{y_{2}}, \dots ,siz_{y_{|Son(x)|}}}\prod\limits_{y \in Son(x)}f_{y} \\ &=(siz_{x}-1)!\prod\limits_{y \in Son(x)}\frac{f_{y}}{siz_{y}!} \\ &=\frac{siz_{x}!}{siz_{x}}\prod\limits_{y \in Son(x)}\frac{\frac{siz_{y}!}{siz_{y}}\prod\limits_{z \in Son(y)}\frac{f_{z}}{siz_{z}!}}{siz_{y}!} \\ &=\frac{siz_{x}!}{\prod\limits_{y \in Subtree(x)}siz_{y}} \end{aligned}\) ,全局答案为 \(f_{1}=\dfrac{n!}{\prod\limits_{i=1}^{n}siz_{i}}\) 。
- 在上面的式子中,重构出的树只有原树上的链底节点的 \(siz\) 不为 \(1\) (不考虑只有一个点的实链)且等于链顶节点在原树上的 \(siz\) 。等价于查询 \(\dfrac{n!}{\prod\limits_{x \in S}siz_{x}}\) ,其中 \(S\) 为链顶集合。难点在于如何维护链顶集合 \(S\) 。
- 此时 \(LCT\) 辅助换根 \(DP\) 已经很容易维护了,详见 [ABC160F] Distributing Integers 。考虑树剖怎么维护。
- 每次
access
操作对一条重链的影响是若干段的链顶被清空,每段的结尾被加入 \(S\) 。故可以对每条重链开一个栈手动模拟set
自浅到深维护极长实链段,由颜色端均摊理论可知时间复杂度为 \(O(n \log n)\) 。 - 具体实现时,每个极长实链段分别维护其在这条重链的开头、实链在这条重链上的转折点(若没有另一条与其相连重链则记为原值,否则记为另一条与其相连的重链链顶)、实链的结尾,每个节点分别维护其所在实链链顶。
- 清空时分讨完全包含或部分包含,前者要特判结尾分裂产生的影响,后者直接分裂成两部分。
点击查看代码
const ll p=998244353; struct node {ll nxt,to; }e[500010]; ll head[500010],fa[500010],inv[500010],siz[500010],son[500010],dep[500010],top[500010],col_top[500010],cnt=0,ans=1; struct quality {mutable ll l;ll r,ed; }it; stack<quality>s[500010]; void add(ll u,ll v) {cnt++;e[cnt].nxt=head[u];e[cnt].to=v;head[u]=cnt; } void dfs1(ll x) {siz[x]=1;dep[x]=dep[fa[x]]+1;for(ll i=head[x];i!=0;i=e[i].nxt){dfs1(e[i].to);siz[x]+=siz[e[i].to];son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];}ans=ans*x%p*inv[siz[x]]%p; } void dfs2(ll x,ll id) {top[x]=id;col_top[x]=x;if(son[x]!=0){dfs2(son[x],id);for(ll i=head[x];i!=0;i=e[i].nxt){if(e[i].to!=son[x]){dfs2(e[i].to,e[i].to);}}} s[top[x]].push((quality){x,x,x}); } void update(ll x) {ans=ans*inv[siz[1]]%p;ll id=x,last=x;while(x!=0){while(s[top[x]].empty()==0&&dep[s[top[x]].top().l]<=dep[x]){it=s[top[x]].top();if(dep[it.r]-(top[it.r]!=top[x])>dep[x])//不完全包含,需要分裂//-(top[it.r]!=top[x]) 是为了找到在本条重链上的转折点{ans=ans*siz[col_top[it.ed]]%p*inv[siz[son[x]]]%p;s[top[x]].top().l=col_top[it.ed]=son[x];}else{if(dep[col_top[it.ed]]<=dep[it.l])//完全包含时在首次遍历到这条实链后就分裂开更新答案{ans=ans*siz[col_top[it.ed]]%p*((top[it.r]!=top[x])?inv[siz[it.r]]:1)%p;//top[it.r]!=top[x] 对应有其他重链连接,否则由极长实链可知其不会产生影响col_top[it.ed]=it.r;}s[top[x]].pop();}}s[top[x]].push((quality){top[x],last,id});//last记录另一条与其相交的重链链顶last=top[x];x=fa[top[x]];}col_top[id]=1; } int main() { #define Isaac #ifdef Isaacfreopen("tree.in","r",stdin);freopen("tree.out","w",stdout); #endifll n,m,x,i;scanf("%lld%lld",&n,&m);inv[1]=1;for(i=2;i<=n;i++){scanf("%lld",&fa[i]);add(fa[i],i);inv[i]=(p-p/i)*inv[p%i]%p;}dfs1(1);dfs2(1,1);for(i=1;i<=m;i++){scanf("%lld",&x);update(x);printf("%lld\n",ans);}return 0; }
- 由实链剖分和
\(T4\) D. 集合操作(set) \(10pts\)
-
部分分
-
\(10pts\) :爆搜。
点击查看代码
ll p,ans=0; deque<ll>s[1010],q; 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 dep,ll w,ll mul) {if(dep!=0){s[dep]=s[dep-1];for(ll i=0;i<s[dep].size();i++){if(s[dep][i]%w==0){q.push_back(i);}}while(q.empty()==0){s[dep].erase(q.back()+s[dep].begin());q.pop_back();}}if(s[dep].size()==0){ans=(ans+dep*mul%p)%p;return;}mul=mul*qpow(s[dep].size(),p-2,p)%p;for(ll i=0;i<s[dep].size();i++){dfs(dep+1,s[dep][i],mul);} } int main() { #define Isaac #ifdef Isaacfreopen("set.in","r",stdin);freopen("set.out","w",stdout); #endifll n,i;cin>>n>>p;for(i=1;i<=n;i++){s[0].push_back(i);}dfs(0,0,1);cout<<ans<<endl;return 0; }
-
\(60pts\)
-
类似 CF280C Game on Tree ,依据期望线性性,考虑每个数被用于增加次数的期望,得到 \(\sum\limits_{i=1}^{n}E(i 被自己删去)=\sum\limits_{i=1}^{n}\frac{1}{d(i)}\) 即为所求。
-
线性筛或暴力求即可。
点击查看代码
ll p,prime[100010],f[100010],low[100010],nu[100010],vis[100010],len=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 isprime(ll n) {memset(vis,0,sizeof(vis));f[1]=1;for(ll i=2;i<=n;i++){if(vis[i]==0){len++;prime[len]=i;nu[i]=1;low[i]=i;f[i]=qpow(2,p-2,p);}for(ll j=1;j<=len&&i*prime[j]<=n;j++){vis[i*prime[j]]=1;if(i%prime[j]==0){low[i*prime[j]]=low[i]*prime[j];nu[i*prime[j]]=nu[i]+1;if(i==low[i]){f[i*prime[j]]=qpow(nu[i*prime[j]]+1,p-2,p);}else{f[i*prime[j]]=f[i/low[i]]*f[low[i*prime[j]]]%p;}}else{low[i*prime[j]]=prime[j];nu[i*prime[j]]=prime[j];f[i*prime[j]]=f[i]*f[prime[j]]%p;}}} } int main() { #define Isaac #ifdef Isaacfreopen("set.in","r",stdin);freopen("set.out","w",stdout); #endifll n,ans=0,i;cin>>n>>p;isprime(n);for(i=1;i<=n;i++){ans=(ans+f[i])%p;}cout<<ans<<endl;return 0; }
-
-
-
正解
总结
- \(T2\) 没想到还可以直接删掉 \(b_{j}\) ,挂了 \(40pts\) 。
- \(T3\) 赛时发现的链长为 \(2\) 的性质无法直接扩展到长度为 \(k \ge 3\) 的性质。