数据结构专项测试2
考完就会T2和T3 40pts了...
T1 5a2m5Yab6aKY
不会。
T2 Q0Y1NzFE
\(std\) 给的做法是在线的,按秩合并并查集,不路径压缩,记录每个点被接到父亲上的时间并在并查集上打标记,每次询问暴跳父亲,如果标记时间合法就更新。
午休时20分钟yy了个离线。离线操作后建出两个并查集的树,第一类建主席树,第二类建线段树,在第二类上打上时间标记,然后在第一类上做子树修改,查询时直接查合法时间区间就好。
写了个 \(std\) 的。
\(code:\)
T2
#include<bits/stdc++.h>
using namespace std;
namespace IO{
typedef long long LL;
#define int LL
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(int x,char sp){
int len=0;
if(x<0) x=-x,putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmax(int& x,int y){ x=x>y?x:y; }
void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=500010;
int n,m,clr[NN];
char op;
vector<pair<int,int>>tag[NN];
struct DSU{
int fa[NN],tim[NN],siz[NN];
void prework(){
for(int i=1;i<=n;i++)
fa[i]=i, siz[i]=1;
}
int getf(int x){ return x==fa[x]?x:getf(fa[x]); }
void merge(int x,int y,int t){
x=getf(x); y=getf(y);
if(x==y) return;
if(siz[x]<siz[y]) swap(x,y);
tim[y]=t; fa[y]=x; siz[x]+=siz[y];
}
}s1,s2;
signed main(){
n=read(); m=read();
s1.prework(); s2.prework();
for(int i=1;i<=n;i++) tag[i].push_back({-1,0});
for(int x,t=1;t<=m;t++){
cin>>op;
if(op=='U') s1.merge(read(),read(),t);
else if(op=='M') s2.merge(read(),read(),t);
else if(op=='A') x=s1.getf(read()), tag[x].push_back({t,tag[x].back().second+s1.siz[x]});
else if(op=='Z') x=s2.getf(read()), clr[x]=t;
else{
x=read();
int tim=clr[x],ans=0,tme;
for(int u=x;u!=s2.fa[u];u=s2.fa[u])
if(clr[s2.fa[u]]>s2.tim[u]) ckmax(tim,clr[s2.fa[u]]);
tme=upper_bound(tag[x].begin(),tag[x].end(),make_pair(tim,0ll))-tag[x].begin()-1;
ans=tag[x].back().second-tag[x][tme].second;
for(int u=x;u!=s1.fa[u];u=s1.fa[u]){
int f=s1.fa[u];
tme=upper_bound(tag[f].begin(),tag[f].end(),make_pair(max(tim,s1.tim[u]),0ll))-tag[f].begin()-1;
ans+=tag[f].back().second-tag[f][tme].second;
}
write(ans,'\n');
}
}
return 0;
}
T3 U1BPSiBDT1Q2
先考虑 \(O(n^2)\) 的 \(DP\) ,设 \(f_u\) 为 \(u\) 做链终点(从下往上)时 \(u\) 子树的最优解,那么有
\[f_u=min_{v\in subtree}\left\{\operatorname{sum}^2(u,v)+\sum_{p\in points}f_p\right\}
\]
其中 \(\operatorname{sum}(u,v)\) 为 \(u,v\) 路径上点权之和, \(subtree\) 为 \(u\) 的子树, \(points\) 为 \(u\) 到 \(v\) 链上所有点不在链上的儿子。
考虑优化。设 \(val_u=\operatorname{sum}(1,u)\) ,那么有
\[f_u=min_{v\in subtree}\left\{(val_v-val_{fa_u})^2+\sum_{p\in points}f_p\right\}
\]
展开式子,有
\[f_u=val_v^2-2\times val_v\times val_{fa_u}+val_{fa_u}^2+\sum_{p\in points}f_p \\
2val_{fa_u}\times val_v+f_u-val_{fa_u}^2=val_v^2+\sum_{p\in points}f_p
\]
这样就化成了斜率优化的形式,对每个点,以 \((val_u,val_u^2+\sum_{p\in points}f_p)\) 为坐标放入 set
中动态维护凸包,求值的时候二分斜率求切线即可。
子树合并时找到重儿子,继承重儿子的 set
,这时重儿子的 \(\sum f\) 会少算,于是将 \(y\) 轴整体平移。
打标记的操作很精妙。
\(code:\)
T3
#include<bits/stdc++.h>
using namespace std;
namespace IO{
#define int long long
#define mp make_pair
#define fi first
#define se second
typedef pair<int,int> PII;
int read(){
int x=0,f=0; char ch=getchar();
while(ch<'0'||ch>'9'){ f|=(ch=='-'); ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return f?-x:x;
} char output[50];
void write(int x,char sp){
int len=0;
if(x<0) x=-x,putchar('-');
do{ output[len++]=x%10+'0'; x/=10; }while(x);
for(int i=len-1;~i;i--) putchar(output[i]); putchar(sp);
}
void ckmax(int& x,int y){ x=x>y?x:y; }
void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=1000010;
int n,f[NN],fa[NN],val[NN],tag[NN],sum[NN];
vector<int>e[NN];
int sqr(int x){ return x*x; }
namespace Hull{
set<PII>s[NN];
double slope(PII x,PII y){ return 1.0*(x.se-y.se)/(x.fi-y.fi); }
bool cmp(PII x,PII y,PII z){ return slope(x,y)<slope(y,z); }
void insert(int i,PII P){
P.se-=tag[i];
auto p=s[i].lower_bound(P);
if(p!=s[i].end()&&p->fi==P.fi)
if(p->se<=P.se) return;
else s[i].erase(p);
while(!s[i].empty()){
p=s[i].lower_bound(P);
if(p==s[i].begin()) break;
--p;
if(p==s[i].begin()) break;
auto q=p; --p;
if(!cmp(*p,*q,P)) s[i].erase(q);
else break;
}
while(!s[i].empty()){
p=s[i].lower_bound(P);
if(p==s[i].end()) break;
auto q=p; ++p;
if(p==s[i].end()) break;
if(!cmp(P,*q,*p)) s[i].erase(q);
else break;
}
p=s[i].lower_bound(P);
if(p==s[i].begin()||p==s[i].end())
return s[i].insert(P),void();
auto q=p; --p;
if(cmp(*p,P,*q)) s[i].insert(P);
}
int calc(int u){
if(s[u].empty()) return LLONG_MAX;
PII ans;
if(s[u].size()==1) ans=*s[u].begin();
else{
int l=s[u].begin()->fi,r=(--s[u].end())->fi,mid;
while(l<r){
mid=l+r+1>>1;
auto p=s[u].lower_bound(mp(mid,-LLONG_MAX));
if(p==s[u].begin()) l=mid;
else{
PII R=*p; PII L=*(--p);
L.se+=tag[u]; R.se+=tag[u];
if(slope(L,R)<2*val[fa[u]]) l=mid;
else r=mid-1;
}
}
ans=*s[u].lower_bound(mp(l,-LLONG_MAX));
}
ans.se+=tag[u];
return sqr(val[fa[u]])-2*val[fa[u]]*ans.fi+ans.se;
}
} using namespace Hull;
void dfs(int u){
val[u]+=val[fa[u]];
int sum=0,son=0;
for(int v:e[u]){
dfs(v); sum+=f[v];
if(s[v].size()>s[son].size()) son=v;
}
swap(s[u],s[son]); tag[u]=tag[son]+sum-f[son];
for(int v:e[u]) if(v^son){
tag[v]+=sum-f[v];
for(PII x:s[v]) x.se+=tag[v], insert(u,x);
}
f[u]=min(calc(u),sum+sqr(val[u]-val[fa[u]]));
insert(u,mp(val[u],sum+sqr(val[u])));
}
signed main(){
n=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=2;i<=n;i++) e[fa[i]=read()].push_back(i);
dfs(1); write(f[1],'\n');
return 0;
}