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 水过去。

posted @ 2025-11-28 21:05  DX3906_ourstar  阅读(0)  评论(0)    收藏  举报