题解 CF1495F Squares
题意简述
给定长度为 \(n\) 的三个数组 \(a,b,p\) ,其中 \(p\) 是一个排列,构建一张 \(n+1\) 个点(点编号 \(1\sim n+1\) )的 DAG ,其中点 \(i(1\le i\le n)\) 向 \(i+1\) 连边权为 \(a_i\) 的边并且向 \(j\) ( \(j\) 是最小的满足 \(j>i\) 并且 \(p_j>p_i\) 的 \(j\) ,如果不存在认为 \(j=n+1\) )连边权为 \(b_i\) 的边,有 \(q\) 次询问,每次询问给定一个点集 \(S\) ,询问 \(1\) 到 \(n+1\) 必须经过 \(S\) 中的所有元素的最短路,其中第 \(i\) 次询问给定的 \(S\) 由第 \(i-1\) 次询问给定的 \(S\) 添加或者删除一个元素得到。
\(1\le n,q\le 2\times 10^5,-10^9\le a_i,b_i\le 10^9\) 。
题目分析
认为点 \(i(1\le i\le n)\) 向 \(i+1\) 连边权为 \(a_i\) 的边为 A 类边,点 \(i(1\le i\le n)\) 向 \(j\) ( \(j\) 是最小的满足 \(j>i\) 并且 \(p_j>p_i\) 的 \(j\) ,如果不存在认为 \(j=n+1\) )连边权为 \(b_i\) 的边为 B 类边。
构造一棵 \(n+1\) 个点的树(点编号 \(0\sim n\) ),使得 \(i(1\le i\le n)\) 的父母结点为 \(j\) ( \(j\) 是最大的满足 \(j<i\) 并且 \(p_j>p_i\) 的 \(j\) ,如果不存在认为 \(j=0\) ),可以发现存在一个经过的点依次是 \(0,1,2,\dots,n\) 的 dfs 方式。
考虑一条原 DAG 中从 \(1\) 号点走到 \(n+1\) 号点的路径,如果经过了以 \(i(1\le i\le n)\) 为起点的 A 类边,我们认为选择了 \(i\) 号点,认为 \(0\) 号点一开始就是被选择的,可以发现一个点最多只会被选择一次,并且一个点 \(i\) 可以被选择当且仅当它在我们构建的树中的所有祖先结点都被选择了(这个比较好证明,假设 \(i\) 的某个祖先结点没有被选择,找到深度最小的没有被选择的祖先结点 \(j\) ,这条路径必然经过了 \(j\) 并且在 \(j\) 这个点选择了 B 类边走了过去,容易发现这条边的终点编号必然大于 \(i\) ,所以无论如何都无法经过 \(i\) ,更别提选择了)。
容易发现一种选择点的方式(满足如果 \(i\) 选了那么 \(i\) 的所有祖先都要被选并且 \(0\) 必须被选择)和一条 \(1\) 到 \(n+1\) 的路径一一对应,并且必须要经过某个点 \(i\) 等价于必须要选择 \(i\) 的父母结点。
假设我们选择的点集为 \(H\) ,我们可以通过选择的点集推出这条路径的长度,这条路径的长度就是这个(其中 \(\operatorname{pa}_u\) 表示 \(u\) 的父母结点):
\[\sum_{u\in H,u\ne 0}a_u+\sum_{u\not\in H,\operatorname{pa}_u\in H}b_u \]如果认为 \(a_0=b_0=0\) ,那么这个式子也可以写成(其中 \(\operatorname{child}_u\) 表示 \(u\) 的孩子结点集合):
\[\sum_{u\in H}(-b_u+a_u+\sum_{v\in \operatorname{child}_u}b_v) \]设:
\[c_u=-b_u+a_u+\sum_{v\in\operatorname{child}_u}b_v \]那么路径长度就是:
\[\sum_{u\in H}c_u \]问题转化成给定一棵 \(n+1\) 个点的有根树(点编号 \(0\sim n\) ,点 \(i\) 权值为 \(c_i\) ),每次询问给定点集 \(S\) 要求求出一个必须包含 \(S\) 和 \(0\) 的最小权值连通块的权值。
先考虑没有必须包含 \(S\) 这个限制,设 \(dp_i\) 表示考虑以 \(i\) 为根的子树,其中必须包含 \(i\) 这个点的最小权值连通块的权值为多少,转移:
\[dp_u=c_u+\sum_{v\in \operatorname{child}_u}\max(0,dp_v) \]考虑加上必须包含 \(S\) 和 \(0\) 这个限制,令:
\[d_u=dp_u-\max(0,dp_u)=\min(0,dp_u) \]设必须包含 \(S\) 和 \(0\) 的最小连通块(这里的最小指的是点的数量尽可能少)为 \(H\) ,那么答案就是:
\[\sum_{u\in H}(c_u+\sum_{v\in\operatorname{child}_u,v\not\in H}\max(0,dp_v)) \] \[=\sum_{u\in H}(dp_u-\sum_{v\in\operatorname{child}_u,v\in H}\max(0,dp_v)) \] \[=dp_0+\sum_{u\in H,u\ne 0}(dp_u-\max(0,dp_u)) \] \[=dp_0+\sum_{u\in H,u\ne 0}d_u \]问题转化成求必须包含 \(S\) 和 \(0\) 的最小连通块的所有点的权值,然后就是套路题了。
时间复杂度 \(\mathcal O(n+q\log_2n)\) 或者 \(\mathcal O((n+q)\log_2n)\) ,可以强制在线。
综上,这道题目就是一道套路题。
参考代码
#include<set>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ch() getchar()
#define pc(x) putchar(x)
using namespace std;
template<typename T>void read(T&x){
static char c;static int f;
for(c=ch(),f=1;c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c>='0'&&c<='9';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>void write(T x){
static char q[65];int cnt=0;
if(x<0)pc('-'),x=-x;
q[++cnt]=x%10,x/=10;
while(x)
q[++cnt]=x%10,x/=10;
while(cnt)pc(q[cnt--]+'0');
}
const int maxn=200005;
int p[maxn],st[maxn],tp,pa[maxn][22],dp[maxn];
long long a[maxn],b[maxn],val[maxn],dis[maxn];
int lca(int x,int y){
if(dp[x]<dp[y])x^=y^=x^=y;
for(int t=dp[x]-dp[y],cn=0;t;t>>=1,++cn)
if(t&1)x=pa[x][cn];
if(x==y)return x;
for(int t=20;t>=0;--t)
if(pa[x][t]!=pa[y][t])
x=pa[x][t],y=pa[y][t];
return pa[x][0];
}
int vis[maxn],cnt[maxn];set<int>s;
long long sum;
long long dist(int x,int y){
return dis[x]+dis[y]-2*dis[lca(x,y)];
}
set<int>::iterator it;
void ins(int x){
it=s.insert(x).first;int l=*(--it);++it;++it;
int r=((it==s.end())?*s.begin():*it);
sum+=dist(l,x)+dist(x,r)-dist(l,r);
}
void del(int x){
it=s.find(x);int l=*(--it);++it;++it;
int r=((it==s.end())?*s.begin():*it);
sum-=dist(l,x)+dist(x,r)-dist(l,r);
--it;s.erase(it);
}
int main(){
int n,q;read(n),read(q);
for(int i=1;i<=n;++i){
read(p[i]);
while(tp&&p[st[tp]]<p[i])--tp;
dp[i]=dp[pa[i][0]=st[tp]]+1;st[++tp]=i;
for(int j=1;(1<<j)<=dp[i];++j)
pa[i][j]=pa[pa[i][j-1]][j-1];
}
for(int i=1;i<=n;++i)read(a[i]),val[i]+=a[i];
for(int i=1;i<=n;++i)read(b[i]),val[i]-=b[i],val[pa[i][0]]+=b[i];
for(int i=n;i>=1;--i)dis[pa[i][0]]+=min(0ll,dis[i]+=val[i]),dis[i]-=min(0ll,dis[i]);
dis[0]+=val[0];for(int i=1;i<=n;++i)dis[i]+=dis[pa[i][0]];cnt[0]=1;s.insert(0);
while(q--){
int x;read(x);
if(vis[x]){
vis[x]=false;
if(!(--cnt[pa[x][0]]))
del(pa[x][0]);
}
else{
vis[x]=true;
if(!(cnt[pa[x][0]]++))
ins(pa[x][0]);
}
write(sum/2+dis[0]),pc('\n');
}
return 0;
}
另外就是这个参考代码和官方题解给的代码应该是一样的,我并不是抄袭官方的,因为我就是出题人 qaq 。