20251128【CSP】模拟
T1 - 排列
给出一个长度为 \(n\) 的数组 \(a\),求一个字典序最小的 \(1∼n\) 排列 \(p\),使得原数组重新排列成 \(a_{p_1},a_{p_2},\cdots,a_{p_n}\) 后<每一个前缀的平均数都大于等于 \(0\)。无解输出 \(-1\)。
容易发现题目中的约束条件等价于重新排列 \(a\) 中的数字使得前缀和数组的每一位都非负。
于是无解就很好判了:不存在满足条件的排列 \(p\),当且仅当 \(\sum\limits_{i=1}^n a_i<0\)。
接下来考虑怎么求 \(p\)。我们设当前已经重排的部分的和为 \(sum\),那么容易发现,找 \(p\) 的过程就是找到一个最小的 \(i\),使得 \(a_i\) 还未被重排且满足 \(sum+a_i\ge0\)。
考虑这个怎么做。联想到分块。
具体地,将 \(a\) 分成 \(\sqrt{n}\) 个块,每块维护块内最大值和存放块内的数的 multiset。对于第 \(i\) 个块,这两样东西分别记作 \(mx_i\) 和 \(val_i\)。每次找 \(p\) 的下一位时,都从左往右找到第一个满足 \(mx_i+sum\ge0\) 的块,再暴力从左往右找到这个块内第一个满足 \(a_j+sum\ge0\) 的 \(j\),这个 \(j\) 就是答案。随后将 \(a_j\) 从 \(val_i\) 中删除,对应更新 \(mx_i\)。正确性比较显然。然后做完了。
时间复杂度 \(O(n^{1.5})\),空间复杂度 \(O(n)\)。
代码:
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<set>
#include<cmath>
#define ll long long
using namespace std;
const int N=1e5+5;
const int M=1e3+5;
int n;
ll sum;
ll a[N];
namespace OIfast{
char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
#define gc getchar()
inline int read(){
int n=0,f=1;static char c=gc;
while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
return n*f;
}
}using namespace OIfast;
namespace FK{
int k,tot;
int L[M],R[M];
short loc[N];
multiset<int,greater<int> >val[M];
ll mx[M];
inline void init(){
k=708,tot=ceil((1.0*n)/(1.0*k));
for(int i=1;i<=tot;++i){
L[i]=(i-1)*k+1,R[i]=min(n,L[i]+k-1);
for(int j=L[i];j<=R[i];++j)loc[j]=i,val[i].insert(a[j]);
mx[i]=*val[i].begin();
}
return ;
}
inline int qry(){
int i=1;for(;i<=tot;++i)if(mx[i]+sum>=0)break ;
for(int j=L[i];j<=R[i];++j)if(a[j]+sum>=0)return sum+=a[j],val[i].erase(val[i].find(a[j])),a[j]=-1e18,mx[i]=(!val[i].empty()?*val[i].begin():-1e18),j;
return 114514;
}
}using namespace FK;
signed main(){
n=read();
ll sum=0;for(int i=1;i<=n;++i)sum+=(a[i]=read());
if(sum<0)return printf("-1\n"),0;
init();for(int i=1;i<=n;++i)printf("%d ",qry());
return printf("\n"),0;
}
提交记录。
T2 - 序列期望
原题 HDU 6410。
有一个由 \(n\) 个随机变量组成的数组 \(x\),其中 \(x_i\) 为满足 \(l_i\le x_i\le r_i\) 的任意整数。记 \(h=\max\limits_{i=1}^n x_i\),又定义某一 \(x\) 的权值为 \(\prod\limits_{i=1}^n(h-x_i+1)\)。求 \(x\) 的权值的期望,答案对 \(10^9+7\) 取模。
我们设所有情况的总数为 \(tot\),所有情况下的权值之和为 \(sum\)。答案即为 \(\frac{sum}{tot}\)。
\(tot\) 是好求的,就是 \(\prod\limits_{i=1}^n(r_i-l_i+1)\)。\(sum\) 是难求的,下面考虑怎么求这个东西。
我们显然不可以枚举 \(x_i\),但可以发现,\(h\) 一定满足 \(\max\limits_{i=1}^n l_i\le h\le\max\limits_{i=1}^n r_i\)。这个值域是 \(10^4\) 级的,可以枚举。
假设我们当前已经枚举到了一个 \(h\),如何计算此时的贡献?
考虑容斥。记 \(sum1\) 为所有的贡献,\(sum2\) 为不合法的贡献,二者相减即为当前的 \(h\) 对 \(sum\) 的贡献。
然后来求 \(sum1\)。枚举每一个 \(1\le i\le n\),定义 \(l^{\prime}=h-l_i+1,r^{\prime}=h-\min(h,r_i)+1\),则 \(l^{\prime}-r^{\prime}+1\) 即为当前的 \(x_i\) 的取值的情况数,\(sum1=\prod\limits_{i=1}^n(l^{\prime}+r^{\prime})\cdot(l^{\prime}-r^{\prime}+1)\)。
类似地,重新定义 \(r^{\prime}=h-\min(h-1,r_i)+1\),则由一样的式子即可求出 \(sum2\)。
那么 \(sum=\sum(sum1-sum2)\)。然后做完了。
时间复杂度 \(O(nV)\),空间复杂度 \(O(n)\)。
代码:
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#include<iostream>
#include<vector>
#define ll long long
using namespace std;
const int N=1e5+5;
int n;
ll tot=1,sum;
int L[N],R[N];
ll x[N];
namespace OIfast{
char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
#define gc getchar()
inline int read(){
int n=0;static char c=gc;
while(!isdigit(c))c=gc;
while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
return n;
}
}using namespace OIfast;
namespace Math{
const ll p=1e9+7;
inline ll mul(ll a,ll b){
return ( (((a%p) + p)%p ) * (((b%p)+p)%p) )%p;
}
inline void mult(ll &a,ll b){
return a=mul(a,b),void();
}
inline void getmx(int &a,int b){
return a=(a>b?a:b),void();
}
inline ll qpow(ll a,int b){
ll res=1;
while(b){
if(b&1)mult(res,a);
mult(a,a),b>>=1;
}
return res;
}
inline ll inv(ll val){
return qpow(val,p-2);
}
}using namespace Math;
signed main(){
n=read();ll tmp=inv(2);
int mn=1,mx=1;
for(int i=1;i<=n;++i){
getmx(mn,L[i]=read()),getmx(mx,R[i]=read());
mult(tot,R[i]-L[i]+1);
}
for(int h=mn;h<=mx;++h){
ll sum1=1,sum2=1;
for(int i=1;i<=n;++i){
ll l_=h-L[i]+1,r_=h-min(h,R[i])+1;
mult(sum1,mul(mul(l_+r_,l_-r_+1),tmp));
}
for(int i=1;i<=n;++i){
ll l_=h-L[i]+1,r_=h-min(h-1,R[i])+1;
mult(sum2,mul(mul(l_+r_,l_-r_+1),tmp));
}
(sum+=((sum1-sum2)%p+p)%p)%=p;
}
return printf("%lld\n",mul(sum,inv(tot))),0;
}
提交记录。
T3 - 树链剖分
原题 hiho 1247。
定义一个树链剖分的代价为树上所有简单路径上的轻边数量之和。给定一个 \(n\) 个结点的树,求每个结点作为根时的最小的树链剖分的代价。
(以下内容若无特殊说明,均指以 \(1\) 为根。)
可以发现,正常地做重链剖分即可找到最小代价。
考虑给定一个树剖方式后,如何求出它的代价。
设 \(sz_u\) 表示 \(u\) 的子树大小。不难发现,对于任意轻儿子 \(u\),其子树内的 \(sz_u\) 个结点全都可以经过连接自己与父亲的这条轻边走到剩下的 \((n-sz_u)\) 个结点。也就是说,代价即为 \(\sum\limits_{u \in \text{ 轻儿子}}sz_u\cdot(n-sz_u)\)。至此 \(O(n^2)\) 暴力已成。
考虑换根时如何以 \(O(1)\) 复杂度转移答案。这是一个换根 DP,然而并不会写。
我们退而求其次,使用记搜。
对于边 \(u\rightarrow v\),我们定义其边权为 \(sz_v\cdot(n-sz_v)\)。显然边 \(v\rightarrow u\) 的边权是一样的,且对于轻边,其边权即为它对总代价的贡献。于是可以先 \(O(n)\) 预处理出边权。
接下来对于边 \(u\rightarrow v\),定义其 \(val\) 为以 \(v\) 为根时的最小代价。这就是我们记搜的东西了。初始时我们把这个东西赋为 \(-1\),表示还没有搜过。
然后考虑对于结点 \(u\) 和它的父亲 \(f\),如何求出边 \(f\rightarrow u\) 的 \(val\)。自然想到这个值就是 \(u\) 连向所有儿子的轻边的边权之和和所有儿子边的 \(val\)。
但是似乎只能较容易地统计所有儿子边的边权之和,却不太好排除掉连向重儿子的边。这时我们发现,边权最大的儿子边必然指向重儿子。因此我们同时求出儿子边的边权最大值,然后减掉即可。
然后做完了。时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。
代码:
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5;
int n;
namespace OIfast{
char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
#define gc getchar()
inline int read(){
int n=0;static char c=gc;
while(!isdigit(c))c=gc;
while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
return n;
}
}using namespace OIfast;
namespace graph{
#define rep for(int i=head[u];~i;i=e[i].nxt)
#define handle int v=e[i].v;if(v==fa)continue ;
int idx=-1;
int head[N];
struct edge{
int v,nxt;ll w,val;
}e[N<<1];
inline void add(int u,int v){
return e[++idx]={v,head[u],114514,-1},head[u]=idx,void();
}
int sz[N];
inline void dfs(int u,int fa){
sz[u]=1;
rep{
handle;
dfs(v,u);
sz[u]+=sz[v];
e[i].w=e[i^1].w=(1LL*sz[v])*(1LL*(n-sz[v]));
}
return ;
}
inline void getmx(ll &a,ll b){
return a=(a>b?a:b),void();
}
inline ll df5(int u,int fa){
ll sum1=0,sum2=0,mx=0;
rep{
handle;
sum1+=((~e[i].val)?e[i].val:e[i].val=df5(v,u));
sum2+=e[i].w,getmx(mx,e[i].w);
}
return sum1+sum2-mx;
}
#undef rep
#undef handle
}using namespace graph;
signed main(){
for(int i=0;i<N;++i)head[i]=-1;
n=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
add(u,v),add(v,u);
}
dfs(1,0);
for(int i=1;i<=n;++i)printf("%lld\n",df5(i,0));
return 0;
}
提交记录。
居然能拿最优解榜二吗,有点意思。
T4 - 矩形
给定一个 \(n\times n\) 的 \(01\) 矩阵,有 \(m\) 次询问,每次询问给定 \(a,b\),求满足四条边上的所有格子都是 \(1\) 的子矩阵的数量。
不会写。好像可以大力卡常 + rp-=INF 水过去。

浙公网安备 33010602011771号