丧病出题人 我比较菜 只写了T1 100/400
T1.约会
题目大意:一棵树,每个点i有权值wi,支持三种操作:1.给定x,询问随机选出i!=j且lca(i,j)=x,wi+wj的期望;2.使一个点的权值加上k;3.使一棵子树内所有点的权值加上k。(n,操作数<=300,000)
思路:对于一个lca(i,j)=x,选出的i和j必然在不同的子树内或者就在x,则
$$ ans[x]=frac{2*(sum (sum[son[x]]*(size[x]-size[son[x]])) +w[x]*(size[x]-1))}{sum (size[son[x]]*(size[x]-size[son[x]]))+size[x]-1} $$
,先预处理出每个点的答案的式子的前半及后半的值,修改单点点权,会使该点及其祖先的答案发生更改,自己的答案把前半式子展开后发现可以O(1)修改,对祖先的修改我们先进行树剖,从一条重链跳到另一条时直接O(1)修改,若在重链上,用mx[x]表示x的重链连向的儿子,则重链上的点的前半式子会被修改k*(size[x]-size[mx[x]]),用线段树维护区间加k,处理询问使再乘上(size[x]-size[mx[x]])即可;再考虑子树加,显然子树内的所有点答案都会加上2k,而这次修改对祖先的贡献的计算方法与单点修改同理。总复杂度O(nlogn^2)(常数较小)。
#include<cstdio> #define ll long long inline int read() { int x,f=1;char c; while((c=getchar())<'0'||c>'9')if(c=='-')f=0; for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0'; return f?x:-x; } #define MN 300000 #define N 524288 struct edge{int nx,t;}e[MN+5]; int h[MN+5],en,l[MN+5],r[MN+5],cnt,f[MN+5],fa[MN+5],mx[MN+5],s[MN+5]; long double a[MN+5]; ll w[MN+5],b[MN+5],t1[N*2+5],t2[N*2+5]; inline void ins(int x,int y){e[++en]=(edge){h[x],y};h[x]=en;} void dfs(int x) { s[x]=1;a[x]=-w[x]; for(int i=h[x];i;i=e[i].nx) { fa[e[i].t]=x;dfs(e[i].t); s[x]+=s[e[i].t];w[x]+=w[e[i].t]; if(s[e[i].t]>s[mx[x]])mx[x]=e[i].t; a[x]-=w[e[i].t]*s[e[i].t]; b[x]-=1LL*s[e[i].t]*s[e[i].t]; } a[x]+=w[x]*s[x]; b[x]+=1LL*s[x]*s[x]-1; } void build(int x,int k) { l[x]=++cnt;f[x]=k; if(mx[x])build(mx[x],k); for(int i=h[x];i;i=e[i].nx)if(e[i].t!=mx[x])build(e[i].t,e[i].t); r[x]=cnt; } void add(ll*t,int l,int r,ll x) { for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1) { if(~l&1)t[l+1]+=x; if( r&1)t[r-1]+=x; } } ll query(ll*t,int k){ll r=0;for(k+=N;k;k>>=1)r+=t[k];return r;} void up(int x,ll z) { for(;;) { if(x!=f[x])add(t2,l[f[x]],l[x]-1,z); if(!fa[f[x]])return; a[fa[f[x]]]+=(s[fa[f[x]]]-s[f[x]])*z; x=fa[f[x]]; } } int main() { freopen("meet.in","r",stdin); freopen("meet.out","w",stdout); int n,m,i,x;char t[5]; n=read();m=read(); for(i=2;i<=n;++i)ins(read(),i); for(i=1;i<=n;++i)w[i]=read(); dfs(1);build(1,1); while(m--) { scanf("%s",t);i=read(); if(t[0]=='Q')printf("%.6lf ", (double)(s[i]>1?2*(query(t1,l[i])+(a[i]+(long double)query(t2,l[i])*(s[i]-s[mx[i]]))/b[i]):0)); if(t[0]=='S')a[i]+=1LL*(s[i]-1)*(x=read()),up(i,x); if(t[0]=='M')add(t1,l[i],r[i],x=read()),up(i,1LL*(r[i]-l[i]+1)*x); } fclose(stdin);fclose(stdout);return 0; }
T3.山脉
题目大意:一条山脉由n个点组成,每个点有一个高度,高度大于左右两个点的称作山峰,每次把一个区间内的高度全部加上一个公差为正的等差数列,并询问此时有多少座山峰。(n,操作数<=100,000,出现的数字的绝对值<=100,000)
思路:显然先把原数组差分,问题转化为每次修改两个点,区间加正数,询问有多少相邻的数字一个大于0一个小于0,先算一遍答案,修改两个点我们可以直接暴力,区间加显然只会当负数变为0或正数、0变成正数时会使答案变化,由于一开始负数/0最多n个,之后每次操作最多使负数/0增多2个,所以这个变化发生的次数最多是O(n)的,我们用线段树维护区间中最大的负数/0,如果区间加后区间内最大的负数非负了或者0超过0了,我们直接修改他,重复这个过程,总复杂度O(nlogn)。
#include<cstdio> #include<algorithm> using namespace std; #define ll long long inline int read() { int x,f=1;char c; while((c=getchar())<'0'||c>'9')if(c=='-')f=0; for(x=c-'0';(c=getchar())>='0'&&c<='9';)x=(x<<3)+(x<<1)+c-'0'; return f?x:-x; } #define MN 100000 #define N 131072 #define INF (1LL<<60) #define L (k<<1) #define R (k<<1|1) ll a[MN+5];int ans[N*2+5]; void change(int k,int x){for(ans[k+=N]=x;k>>=1;)ans[k]=ans[L]+ans[R];} struct data { ll mf,m0;int pf,p0; data(ll mf=0,int pf=0,ll m0=0,int p0=0):mf(mf),pf(pf),m0(m0),p0(p0){} data(int x){pf=p0=x;mf=a[x]<0?a[x]:-INF;m0=a[x]?-INF:0;} inline friend data operator+(data a,data b) { return data(max(a.mf,b.mf),a.mf>b.mf?a.pf:b.pf, max(a.m0,b.m0),a.m0>b.m0?a.p0:b.p0); } inline void operator+=(ll x){mf+=x;m0+=x;} }; struct node{int l,r;ll mk;data x;}t[MN*4+5]; inline void up(int k){t[k].x=t[L].x+t[R].x;} inline void add(int k,ll x){t[k].x+=x;t[k].mk+=x;} inline void down(int k){if(t[k].mk)add(L,t[k].mk),add(R,t[k].mk),t[k].mk=0;} void build(int k,int l,int r) { if((t[k].l=l)==(t[k].r=r)){t[k].x=data(l);return;} int mid=l+r>>1; build(L,l,mid);build(R,mid+1,r);up(k); } ll query(int k,int x) { while(t[k].l<t[k].r)down(k),k=x>t[k].l+t[k].r>>1?R:L; return a[t[k].l]+t[k].mk; } void add(int k,int x,ll ad) { while(t[k].l<t[k].r)down(k),k=x>t[k].l+t[k].r>>1?R:L; a[t[k].l]+=t[k].mk+ad;t[k].mk=0; change(x,query(1,x-1)>0&&a[t[k].l]<0); change(x+1,a[t[k].l]>0&&query(1,x+1)<0); t[k].x=data(t[k].l); while(k>>=1)up(k); } void add(int k,int l,int r,int ad) { if(t[k].l==l&&t[k].r==r){add(k,ad);return;} int mid=t[k].l+t[k].r>>1;down(k); if(r<=mid)add(L,l,r,ad); else if(l>mid)add(R,l,r,ad); else add(L,l,mid,ad),add(R,mid+1,r,ad); up(k); } data query(int k,int l,int r) { if(t[k].l==l&&t[k].r==r)return t[k].x; int mid=t[k].l+t[k].r>>1;down(k); if(r<=mid)return query(L,l,r); if(l>mid)return query(R,l,r); return query(L,l,mid)+query(R,mid+1,r); } int main() { int n,m,i,l,r,a1,d;data x; n=read();m=read(); for(i=1;i<=n;++i)a[i]=read(); for(i=1;i<n;++i)a[i]=a[i+1]-a[i];a[n]=0; for(i=2;i<n;++i)if(a[i-1]>0&&a[i]<0)change(i,1); build(1,0,n); printf("%d ",ans[1]); while(m--) { l=read();r=read();a1=read();d=read(); if(l>1)add(1,l-1,a1); if(l<r)for(add(1,l,r-1,d);;) if((x=query(1,l,r)).mf>=0)add(1,x.pf,0); else if(x.m0>0)add(1,x.p0,0); else break; if(r<n)add(1,r,-a1-1LL*(r-l)*d); printf("%d ",ans[1]); } }