20260409 紫题训练

T1

下文将“有丰富能源”的点称为关键点。

\(f_i\) 为将 \(i\) 与子树内所有关键点隔开的最小代价,答案即为 \(f_1\)

转移:

\[f_i= \sum_{p\in son_i}\left\{\begin{matrix} w(x,p)&p\text{为关键点}\\ \min(w(x,p),f_p)&p\text{不为关键点} \end{matrix}\right. \]

但若对于每个询问都做一遍 \(\mathcal O(n)\) DP 肯定无法通过。

但注意到题目限制了关键点在所有询问的总数,而观察 DP 的状态也只与关键点有关,可以想办法将无关的点都删除。

事实上,删除无关的点后构建出的树就是虚树,这里有一种好写好记的构建方式

注意参考代码实现中的 连边,如有边权 就是 distance(lc,a[i+1]) 是不准确的,应随要解决的问题而定,如本题中应为这两点间的最小边权。

注意将 \(1\) 号点加入虚树。

#include<bits/stdc++.h>
#define N 500005
using namespace std;
using ll=long long;
struct edge{int x,w;};
int f[N][20],mx[N][20];
vector<edge>s[N];bool c[N];
int n,m,idx,a[N],dfn[N],dep[N];
void dfs(int x,int fa){
	dfn[x]=++idx,dep[x]=dep[f[x][0]=fa]+1;
	for(auto p:s[x]) if(p.x^fa) mx[p.x][0]=p.w,dfs(p.x,x);
}
int LCA(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;~i;i--)
		if(dep[f[u][i]]>=dep[v]) u=f[u][i];
    if(u==v) return u;
    for(int i=19;~i;i--)
		if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
    return f[u][0];
}
int getmin(int x,int fa){
	int res=INT_MAX;
	for(int i=19;~i;i--)
		if(dep[x]>=dep[fa]+(1<<i))
			res=min(res,mx[x][i]),x=f[x][i];
	return res;
}
ll DP(int x){
	ll res=0;
	for(auto p:s[x])
		if(c[p.x]) res+=p.w;
		else res+=min((ll)p.w,DP(p.x));
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;i++)
		scanf("%d%d%d",&u,&v,&w),
		s[u].push_back({v,w}),
		s[v].push_back({u,w});
	dfs(1,0),scanf("%d",&m);
	for(int i=1;i<=n;i++) s[i].clear();
	for(int j=1;j<20;j++) for(int i=1;i<=n;i++)
		f[i][j]=f[f[i][j-1]][j-1],mx[i][j]=min(mx[i][j-1],mx[f[i][j-1]][j-1]);
	while(m--){
		scanf("%d",&n);
		auto cmp=[](int x,int y){return dfn[x]<dfn[y];};
		for(int i=1;i<=n;i++) scanf("%d",a+i),c[a[i]]=true;
		a[++n]=1,sort(a+1,a+n+1,cmp);
		for(int i=1,k=n;i<k;i++) a[++n]=LCA(a[i],a[i+1]);
		sort(a+1,a+n+1,cmp),n=unique(a+1,a+n+1)-a-1;
		for(int i=1,lca;i<n;i++) lca=LCA(a[i],a[i+1]),s[lca].push_back({a[i+1],getmin(a[i+1],lca)});
		printf("%lld\n",DP(1));for(int i=1;i<=n;i++) c[a[i]]=false,s[a[i]].clear();
	}
	return 0;
}

T2

答案的分子为:

\[\sum_{i=L}^R (x_i-x_t)^2\\ =\sum_{i=L}^R x_i^2-2x_t\sum_{i=L}^R x_i+(R-L+1)x_t^2 \]

答案的分母为:

\[\sum_{i=L}^R (x_i-x_t)(y_i-y_t)\\ =\sum_{i=L}^R x_iy_i-x_t\sum_{i=L}^R y_i-y_t\sum_{i=L}^R x_i+(R-L+1)x_ty_t \]

只需维护 \(x,y,xy,x^2\) 的区间和。

当给 \(x_{L\sim R}+S,y_{L\sim R}+T\) 时:

\[\sum_{i=L}^R x_i'=\sum_{i=L}^R x_i+(R-L+1)S\\ \sum_{i=L}^R y_i'=\sum_{i=L}^R y_i+(R-L+1)T\\ \sum_{i=L}^R (x_i')^2=\sum_{i=L}^R x_i^2+2S\sum_{i=L}^R x_i+(R-L+1)S^2\\ \sum_{i=L}^R x_i'y_i'=\sum_{i=L}^R x_iy_i+S\sum_{i=L}^R y_i+T\sum_{i=L}^R x_i+(R-L+1)ST \]

都可以 \(\mathcal O(1)\) 更新。

另一个操作可以看作 \(x_i,y_i\gets i\) 再做一操作:

\[\sum_{i=L}^R i=\frac{(L+R)(R-L+1)}{2}\\ \sum_{i=L}^R i^2=\dfrac{R(R+1)(2R+1)-L(L+1)(2L+1)}{6}\\ \]

也可以 \(\mathcal O(1)\) 计算,用线段树维护即可。

#include<bits/stdc++.h>
#define N 100005
using namespace std;
using db=double;
using ll=long long;
int n,m;
struct data{
	db x,y,x2,xy;
	data(){x=y=x2=xy=0;}
	data(db a,db b,db c,db d){x=a,y=b,x2=c,xy=d;}
	data &operator+=(const data &t){return *this=*this+t;}
	data operator+(const data &t){return {x+t.x,y+t.y,x2+t.x2,xy+t.xy};}
};
class SGT{
	#define l(i) ((i)<<1)
	#define r(i) ((i)<<1|1)
	#define tx(i) tr[i].tx
	#define ty(i) tr[i].ty
	#define t(i) tr[i].tag
	#define x(i) tr[i].v.x
	#define y(i) tr[i].v.y
	#define xy(i) tr[i].v.xy
	#define x2(i) tr[i].v.x2
	private:
		struct node{
			data v;
			bool tag;
			db tx,ty;
		}tr[N<<2];
		void add(int p,int l,int r,ll S,ll T){
			tx(p)+=S,ty(p)+=T;
			x2(p)+=(2*S*x(p))+(r-l+1)*S*S;
			xy(p)+=S*y(p)+T*x(p)+(r-l+1)*S*T;
			x(p)+=(r-l+1)*S,y(p)+=(r-l+1)*T;
		}
		void set(int p,int l,int r){
			tx(p)=ty(p)=0,t(p)=true,x(p)=y(p)=(db)(l+r)*(r-l+1)/2,l--;
			x2(p)=xy(p)=((db)r*(r+1)*(2*r+1)-(db)l*(l+1)*(2*l+1))/6;
		}
		void down(int p,int l,int r){
			int mid=l+r>>1;
			if(t(p)) set(l(p),l,mid),set(r(p),mid+1,r);
			add(l(p),l,mid,tx(p),ty(p));
			add(r(p),mid+1,r,tx(p),ty(p));
			t(p)=tx(p)=ty(p)=0;
		}
		void up(int p){tr[p].v=tr[l(p)].v+tr[r(p)].v;}
	public:
		void upd1(int ql,int qr,db S,db T,int p=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return add(p,l,r,S,T);
			int mid=l+r>>1;down(p,l,r);
			if(ql<=mid) upd1(ql,qr,S,T,l(p),l,mid);
			if(qr>mid) upd1(ql,qr,S,T,r(p),mid+1,r);
			up(p);
		}
		void upd2(int ql,int qr,int p=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return set(p,l,r);
			int mid=l+r>>1;down(p,l,r);
			if(ql<=mid) upd2(ql,qr,l(p),l,mid);
			if(qr>mid) upd2(ql,qr,r(p),mid+1,r);
			up(p);
		}
		data query(int ql,int qr,int p=1,int l=1,int r=n){
			if(ql<=l&&qr>=r) return tr[p].v;
			int mid=l+r>>1;data res;down(p,l,r);
			if(ql<=mid) res+=query(ql,qr,l(p),l,mid);
			if(qr>mid) res+=query(ql,qr,r(p),mid+1,r);
			return res;
		}
}tr;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),tr.upd1(i,i,x,0);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),tr.upd1(i,i,0,x);
	while(m--){
		int op,l,r;db S,T;
		scanf("%d%d%d",&op,&l,&r);
		if(op==3) tr.upd2(l,r);
		if(op>1){
			scanf("%lf%lf",&S,&T);
			tr.upd1(l,r,S,T);
			continue;
		}
		auto p=tr.query(l,r);
		db xt=p.x/(r-l+1),yt=p.y/(r-l+1);
		db fm=p.x2-2*xt*p.x+(r-l+1)*xt*xt;
		db fz=p.xy-xt*p.y-yt*p.x+(r-l+1)*xt*yt;
		printf("%.10lf\n",fz/fm);
	}
	return 0;
}

T3

不难发现一个长度为 \(n\) 的数列用 Gobo sort 的期望轮数为 \(\dfrac{n!}{\text{有序数列数}}\)

\(b_x=\sum_{i=1}^n [a_i=x]\),设 \(k=\max_{i=1}^n a_i\)

有序数列数为 \(\prod_{i=1}^k b_i!\)

要求期望轮数最多,即要让 \(\prod_{i=1}^k b_i!\) 最小。

由阶乘的增长速度越来越快可知尽量平均 \(b\) 是最优的。

于是在 \([l,r]\) 中填 \(m\) 个数就变为选 \(m\)\(x\in [l,r]\)\(b_x\gets b_x+1\), 要让 \(b\) 尽量平均。

可以先将 \(b_{l\sim r}\) 排序,从小往大填,每次给前缀加 \(1\),每一个 \(b_i\) 加到 \(b_{i+1}\) 为止,这样可以保证最平均。

但值域很大无法求出 \(b\) 数组,记录 \(c_x=\sum_{i=l}^r [b_i=x]\),这样就不用排序,前缀加变为给 \(c\) 单点修改。

\(c\) 修改的具体过程:

若当前要给 \(b_i\) 做前缀加到 \(b_i+1\),由上述过程可以看出 \(\forall j<i,b_j=b_i\),因此相当于 \(c_{b_i+1}\gets c_{b_i},c_{b_i}\gets 0\),消耗 \(c_{b_i}\) 个数。

当剩余要填的数 \(t<c_{b_i}\) 时,\(c_{b_i+1}\gets t,c_{b_i}\gets c_{b_i}-t\)

这个过程是 \(\mathcal O(\dfrac{m}{n})\) 的。

注意填完之后将 \(i\notin [l,r]\)\(b_i\) 统计到 \(c\) 中。

\[\prod_{i=1}^k b_i!\\ =\prod_{i=0}^\infty (i!)^{\sum_{j=1}^k [b_j=i]}\\ =\prod_{i=0}^\infty (i!)^{c_i} \]

答案可以 \(\mathcal O(\dfrac{m}{n}\log V)\) 求出。

但当 \(m\) 较大 \(n\) 较小时,计算 \(c\) 和统计答案时时间复杂度不对。

此时可以特判:

若处理到了 \(c_{n}\),则后面的 \(c\) 此时都是 \(0\)。设剩余要填的数为 \(t\),最终结果就是

\[c_{n+\lceil\frac{t}{c_n}\rceil}=t\%c_n\\ c_{n+\lfloor\frac{t}{c_n}\rfloor}=c_n-t\%c_n \]

\(p=n+\lfloor\frac{t}{c_n}\rfloor\),则答案为 \(p!^{c_p}(p+1)!^{c_{p+1}}\),可以预处理 \(0\sim m\) 的阶乘 \(\mathcal O(\log V)\) 算出。

#include<bits/stdc++.h>
#define x first
#define v second
#define N 200005
#define M 10000005
using namespace std;
const int P=998244353;
int n,m,l,r,c[N],f[N+M];
int qpow(int x,int y){
	int res=1;while(y){
		if(y&1) res=1ll*res*x%P;
		x=1ll*x*x%P,y>>=1;
	}return res;
}
int main(){
	int T;scanf("%d",&T);
	f[0]=1;for(int i=1;i<N+M;i++) f[i]=1ll*f[i-1]*i%P;
	while(T--){
		unordered_map<int,int>a;
		scanf("%d%d%d%d",&n,&m,&l,&r);
		int tot=f[n+m],ans=1;c[0]=r-l+1,a.clear();
		for(int i=1,x;i<=n;i++) c[i]=0,scanf("%d",&x),a[x]++;
		for(auto p:a) if(p.x>=l&&p.x<=r) c[p.v]++,c[0]--;
		for(int i=0;m&&i<n;i++)
			if(m>=c[i]) c[i+1]+=c[i],m-=c[i],c[i]=0;
			else c[i+1]+=m,c[i]-=m,m=0;
		if(m){
			int t=m%c[n],p=n+m/c[n];
			ans=1ll*ans*qpow(f[p],c[n]-t)%P;
			ans=1ll*ans*qpow(f[p+1],t)%P,c[n]=0;
		}for(auto p:a) if(p.x<l||p.x>r) c[p.v]++;
		for(int i=0;i<=n;i++) ans=1ll*ans*qpow(f[i],c[i])%P;
		printf("%lld\n",1ll*tot*qpow(ans,P-2)%P);
	}
	return 0;
}
posted @ 2026-04-09 20:27  Jokersen  阅读(3)  评论(0)    收藏  举报