题面:https://www.cnblogs.com/Juve/articles/11523567.html
影子:
暴力方法:枚举每一对点暴力统计最小权
优化:考虑并查集,枚举每个点,如果没有被访问过,那么尝试把这两个点加到一个集合里
维护每一个点作为最小权时的树上路径的两个端点,合并时维护即可
将所有点按照权值从大到小排序,对于将当前点和与其相连的所有点依次合
并到一个集合中。并查集需要维护当前集合中的最长路径长度和对应的两个端点。在合并两个集合后,最终集合的最长路一定只有两类情况:一类是其中一个集合的最长路,一共有 2 种;一类是由两个集合的最长路的端点互相连接而成,一共有 2×2=4 种。需要用到最近公共祖先的算法预处理求两点在树上的距离,离线处理即可。每次合并并查集之后用当前点的权值乘以最长路的总长度来更新最优结果即可。即使这个点不在当前合并后的集合的最长路上也是没有问题的,因为如果这样的话,必然已经在之前得到了对应的结果,这次合并不会对最终结果产生影响。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<algorithm> #define int long long using namespace std; const int MAXN=1e5+5; int t,n,d[MAXN],ans=0; int to[MAXN<<1],nxt[MAXN<<1],pre[MAXN],val[MAXN<<1],cnt=0; inline void add(int u,int v,int w){ cnt++,to[cnt]=v,nxt[cnt]=pre[u],pre[u]=cnt,val[cnt]=w; } int f[MAXN][19],deep[MAXN],dis[MAXN]; void dfs(int x){ for(int i=1;i<=18;i++){ if(f[x][i-1]) f[x][i]=f[f[x][i-1]][i-1]; } for(int i=pre[x];i;i=nxt[i]){ if(to[i]!=f[x][0]){ deep[to[i]]=deep[x]+1; dis[to[i]]=dis[x]+val[i]; f[to[i]][0]=x; dfs(to[i]); } } } int LCA(int x,int y){ if(deep[x]<deep[y]) swap(x,y); for(int i=18;i>=0;i--) if(deep[f[x][i]]>=deep[y]) x=f[x][i]; if(x==y) return x; for(int i=18;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } struct node{ int val,id; friend bool operator < (node a,node b){ return a.val>b.val; } }p[MAXN]; bool vis[MAXN]; int fa[MAXN]; struct data{ int d,st,ed; }q[MAXN]; int find(int x){ return fa[x]=(fa[x]==x?x:find(fa[x])); } void unionn(int x,int y){ x=find(x),y=find(y); if(x!=y){ fa[y]=x; data mx; if(q[x].d>q[y].d) mx=q[x]; else mx=q[y]; int lca=LCA(q[x].st,q[y].st); int dist=dis[q[x].st]+dis[q[y].st]-2*dis[lca]; if(mx.d<dist) mx=(data){dist,q[x].st,q[y].st}; lca=LCA(q[x].st,q[y].ed); dist=dis[q[x].st]+dis[q[y].ed]-2*dis[lca]; if(mx.d<dist) mx=(data){dist,q[x].st,q[y].ed}; lca=LCA(q[x].ed,q[y].ed); dist=dis[q[x].ed]+dis[q[y].ed]-2*dis[lca]; if(mx.d<dist) mx=(data){dist,q[x].ed,q[y].ed}; lca=LCA(q[x].ed,q[y].st); dist=dis[q[x].ed]+dis[q[y].st]-2*dis[lca]; if(mx.d<dist) mx=(data){dist,q[x].ed,q[y].st}; q[x]=mx; } } signed main(){ scanf("%lld",&t); while(t--){ memset(dis,0,sizeof(dis)); memset(pre,0,sizeof(pre)); memset(f,0,sizeof(f)); memset(deep,0,sizeof(deep)); memset(vis,0,sizeof(vis)); ans=0;cnt=0; scanf("%lld",&n); for(int i=1;i<=n;++i){ fa[i]=i; q[i]=(data){0,i,i}; scanf("%lld",&d[i]); p[i]=(node){d[i],i}; } for(int i=1,u,v,w;i<n;++i){ scanf("%lld%lld%lld",&u,&v,&w); add(u,v,w),add(v,u,w); } sort(p+1,p+n+1); deep[1]=1; dfs(1); for(int i=1;i<=n;++i){ int x=p[i].id; vis[x]=1; for(int j=pre[x];j;j=nxt[j]){ int y=to[j]; if(!vis[y]) continue; unionn(x,y); } ans=max(ans,q[x].d*d[x]); } printf("%lld\n",ans); } return 0; }
玫瑰花精:
可以考虑线段树。首先我们对区间[1..n]建立一棵线段树。对于每一个节点,
维护 4 个值。分别是 l,r,mid,p。
l表示在当前结点线段树所在区间最左边的花精所在的位置,r 表示最右边的花精所在的位置。
mid 表示在这个小区间[l,r]中的两只花精之间的最长距离除以 2 后的值。
p 表示取 mid 值时所在的紧邻的两只花精的中间位置,也就是在[l,r]中的答案值。
对于 1 询问:访问线段树的第一个节点,我们比较 l-1,n-r,mid 的值哪个更大,就选哪个,它们的答案依次是 1,n,p。
假设我们求得的位置是 fairy[x]。然后访问[fairy[x],fairy[x]]所在的线段树的叶子节点,初始化它的值,然后回溯,进行合并。
对于 tr[x].l 与 tr[x].r 可以通过两个儿子的 l,r 信息得出。
对于 tr[x].mid值,首先在左右儿子的 mid 值中去一个最大的值。
其次考虑一种情况,就是夹在两个线段之间的距离,可以通过(tr[x<<1|1].l-tr[x<<1].r)/2 的值得出在于 mid进行比较,然后 p 就随着 mid
的值的更新而更新。
对于 2 询问:访问询问花精所在的位置,直接将它的叶子节点[fairy[x],fairy[x]]删除,然后回溯时,再做一次合并操作。
#include<cstdio> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int MAXN=2e5+5; const int MAXM=1e6+5; int n,m,pos[MAXM]; struct node{ int l,r,mid,p; }tr[MAXN<<2]; int get_pos(){ if(tr[1].l==0) return 1; int res=max(tr[1].l-1,max(n-tr[1].r,tr[1].mid)); if(res==tr[1].l-1) return 1; else if(res==tr[1].mid) return tr[1].p; else return n; } void pushup(int k){ if(tr[k<<1].l==0||tr[k<<1|1].l==0) tr[k].l=tr[k<<1].l+tr[k<<1|1].l; else tr[k].l=tr[k<<1].l; tr[k].r=max(tr[k<<1|1].r,tr[k<<1].r); tr[k].mid=max(tr[k<<1].mid,tr[k<<1|1].mid); int p1=tr[k<<1].r,p2=tr[k<<1|1].l; if(p1&&p2) tr[k].mid=max(tr[k].mid,(p2-p1)/2); if(tr[k].mid==tr[k<<1].mid) tr[k].p=tr[k<<1].p; else if(tr[k].mid==(p2-p1)/2) tr[k].p=(p2+p1)/2; else tr[k].p=tr[k<<1|1].p; } void update(int k,int l,int r,int pos){ if(l==r){ tr[k].mid=tr[k].p=0; tr[k].l=tr[k].r=l; return ; } int mid=(l+r)>>1; if(pos<=mid) update(k<<1,l,mid,pos); else update(k<<1|1,mid+1,r,pos); pushup(k); } void change(int k,int l,int r,int pos){ if(l==r){ tr[k].mid=tr[k].p=tr[k].l=tr[k].r=0; return ; } int mid=(l+r)>>1; if(pos<=mid) change(k<<1,l,mid,pos); else change(k<<1|1,mid+1,r,pos); pushup(k); } int main(){ scanf("%d%d",&n,&m); while(m--){ int opt,x; scanf("%d%d",&opt,&x); if(opt==1){ pos[x]=get_pos(); update(1,1,n,pos[x]); printf("%d\n",pos[x]); }else{ change(1,1,n,pos[x]); pos[x]=0; } } return 0; }