UOJ87 mx的仙人掌
这里没有用传统的方点外接圆点的做法,而是方点虚树上儿子跳到方点所在环上单调队列处理,本质上是一样的.
注意这是仙人掌的写法,求点双不一样...
code
//爽!
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=900900;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=getchar();
}
while('0'<=ch&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
int n,m;
#define pr pair<int,ll>
#define mk make_pair
#define fi first
#define se second
map<int,int>e[N];
vector<pr>mp[N];
void add(int u,int v,int w){//重边(二元环单独处理)
if(e[u].count(v))e[u][v]=min(e[u][v],w);
else e[u][v]=w;
if(e[v].count(u))e[v][u]=min(e[v][u],w);
else e[v][u]=w;
}
vector<int>edge[N];
int tot,dfn[N],low[N],Index,fa[N];
ll dis[N],sum[N];
void solve(int x,int rt,int Edge){
ll len=dis[x]-dis[rt]+Edge,tmp;
++tot;
sum[tot]=len;
mp[rt].push_back(mk(tot,0));
mp[tot].push_back(mk(rt,0));
for(int i=x;i!=rt;i=fa[i]){
tmp=min(dis[i]-dis[rt],len-dis[i]+dis[rt]);
mp[tot].push_back(mk(i,tmp));
mp[i].push_back(mk(tot,tmp));
}
}
void tarjan(int u){
dfn[u]=low[u]=++Index;
int v;
for(map<int,int> :: iterator it=e[u].begin();it!=e[u].end();++it){//递归调用,不然迭代器会出错
v=it->fi;
if(v==fa[u])continue;
if(!dfn[v]){
dis[v]=dis[u]+it->se;
fa[v]=u;
tarjan(v);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]){
mp[u].push_back(mk(v,it->se));
mp[v].push_back(mk(u,it->se));
}
//我现行的圆方树建法会忽略二环
}
else low[u]=min(low[u],dfn[v]);
}
for(map<int,int> :: iterator it=e[u].begin();it!=e[u].end();++it){
v=it->fi;
if(fa[v]!=u&&dfn[v]>=dfn[u])solve(v,u,it->se);
}
}
int f[N][20],dep[N],l[N],num;
ll Len[N];
void dfs(int u,int fa){
f[u][0]=fa,dep[u]=dep[fa]+1;
l[u]=++num;
// printf("fa=%d son=%d
",fa,u);
// printf("tree %d %lld
",u,Len[u]);
for(int v,i=0;i<mp[u].size();++i){
ll w;
v=mp[u][i].fi;
if(v==fa)continue;
w=mp[u][i].se;
Len[v]=Len[u]+w;
dfs(v,u);
}
}
void prework(){
for(int i=1;i<=19;++i){
for(int j=1;j<=tot;++j){
f[j][i]=f[f[j][i-1]][i-1];
}
}
}
inline int getlca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=19;i>=0;--i){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(dep[x]==dep[y])break;
}
if(x==y)return x;
for(int i=19;i>=0;--i)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int a[N],s[N],top;
bool cmp(int aa,int bb){
return l[aa]<l[bb];
}
void build(int x){
if(top==0){
s[++top]=x;
return;
}
int lca=getlca(s[top],x);
while(top>1&&dep[lca]<dep[s[top-1]]){
edge[s[top-1]].push_back(s[top]);
--top;
}
while(top>0&&dep[lca]<dep[s[top]]){
edge[lca].push_back(s[top]);
--top;
}
if(top==0||s[top]!=lca)s[++top]=lca;
s[++top]=x;
}
//这里比较刚,没有用传统的方点外接圆点的做法,直接在方点处讨论(因为找不到看得懂的题解了)
ll ans,dp[N],g[N],h[N];
int q[N],c[N];
//int jump(int pos,int stp){
// for(int i=19;i>=0;--i)
// if(stp>=(1<<i)){
// pos=f[pos][i];
// stp-=(1<<i);//学会倍增跳
// }
// return pos;
//}
void jump(int & u, int k) {
for(int i = 20 - 1; ~ i; --i) {
if(k >= (1 << i)) {
k -= 1 << i;
u = f[u][i];
}
}
}
bool cmp2(int aa,int bb){
return l[aa]>l[bb];
}
void circle(int u,int size){
// printf("%d %d
",u,size);
// printf("cir=%lld
",sum[u]);
sort(c+1,c+size+1,cmp2);
// reverse(c+1,c+size+1);
//这里两种都可以,但上面一种多个log好理解
//下面一种讲究建边细节
//具体是我们要从环的底端到顶端,考虑vector存储,那么底端要先加进c,那么底端先访问,即底端先建边,等价于底端先出进虚树的栈,所以底端dfn要大,所以建圆方树时要先建方点到顶端依次到底端的圆点,然后tarjan处理的时候自己注意
for(int i=1;i<=size;++i){
// printf("%d ",c[i]);
h[i]=dis[c[i]];
h[i+size]=h[i]+sum[u];//注意用的是原仙人掌上环上的值
g[i]=dp[c[i]];
g[i+size]=g[i];
//复制一遍
// printf("加油=%lld %lld
",h[i],g[i]);
}
// puts("");
int l,r;
l=r=1;
q[r]=1;
size*=2;
for(int i=1;i<=size;++i){
// printf("加油=%lld %lld
",h[i],g[i]);
}
for(int i=2;i<=size;++i){
while(l<=r&&2LL*(h[i]-h[q[l]])>sum[u])++l;//最短路
if(l<=r)ans=max(ans,g[i]+g[q[l]]+h[i]-h[q[l]]);
while(l<=r&&g[q[r]]-h[q[r]]<=g[i]-h[i])--r;
q[++r]=i;
}
// puts("");
}
void tree_dp(int u){
dp[u]=0;
int size=0;
for(int v,i=0;i<edge[u].size();++i){
v=edge[u][i];
tree_dp(v);
}
//注意因为要保留栈,所以必须把递归分开写
for(int v,i=0;i<edge[u].size();++i){
v=edge[u][i];
if(u<=n){
ans=max(ans,dp[u]+dp[v]+Len[v]-Len[u]);
//树部分
}
else{
int son=v;
jump(son,dep[v]-dep[u]-1);//son要么就是v要么本不存在于虚树上
dp[son]=dp[v]+Len[v]-Len[son];//跳到环上
c[++size]=son;
}
dp[u]=max(dp[u],dp[v]+Len[v]-Len[u]);//圆方树上传贡献
}
if(u>n)circle(u,size);//环部分
edge[u].clear();//清空
}
int main(){
// freopen("s.in","r",stdin);
n=read(),m=read();
tot=n;
int u,v,w;
for(int i=1;i<=m;++i){
u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);
}
tarjan(1);
// puts("dis");
// for(int i=1;i<=n;++i)printf("%lld ",dis[i]);
// puts("dis");
dfs(1,0);
prework();
int Q=read();
while(Q--){
int size=read();
for(int i=1;i<=size;++i)a[i]=read();
sort(a+1,a+size+1,cmp);
size=unique(a+1,a+size+1)-a-1;//记得去重(题目要求)
top=0;
for(int i=1;i<=size;++i)build(a[i]);
while(top>1){
edge[s[top-1]].push_back(s[top]);
--top;
}
ans=0;
tree_dp(s[top]);//也可以不强制1为根
// for(int i=1;i<=size;++i)printf("%lld ",dp[i]);puts("");
// puts("");
printf("%lld
",ans);
}
return 0;
}