20260409 紫题训练
T1
下文将“有丰富能源”的点称为关键点。
设 \(f_i\) 为将 \(i\) 与子树内所有关键点隔开的最小代价,答案即为 \(f_1\)。
转移:
但若对于每个询问都做一遍 \(\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
答案的分子为:
答案的分母为:
只需维护 \(x,y,xy,x^2\) 的区间和。
当给 \(x_{L\sim R}+S,y_{L\sim R}+T\) 时:
都可以 \(\mathcal O(1)\) 更新。
另一个操作可以看作 \(x_i,y_i\gets i\) 再做一操作:
也可以 \(\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\) 中。
答案可以 \(\mathcal O(\dfrac{m}{n}\log V)\) 求出。
但当 \(m\) 较大 \(n\) 较小时,计算 \(c\) 和统计答案时时间复杂度不对。
此时可以特判:
若处理到了 \(c_{n}\),则后面的 \(c\) 此时都是 \(0\)。设剩余要填的数为 \(t\),最终结果就是
设 \(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;
}