CF835F Roads in the Kingdom/P1399 [NOI2013]快餐店
真好玩,CF考NOI原题。如果您看懂了题目的话发现这两题只有两个差别:1. 数据范围;2. 第一题的答案除以2保留一位小数就是第二题答案。成功双倍经验
开始讲做法
首先按照套路找环,记录环上权值,同时对于每一个环上的点跑出它子树内的最长链 \(f_i\)。
话说我看到了一个比较方便的找环同时记录环上的方法,比我在 P4381 [IOI2008]Island 里头自己yy的屎山代码好多了,就学了一下。
接下去发掘一下性质。
首先,断完边原图仍然联通,那么必然是一颗树,必然只能断环上的边
显然枚举一下断哪条边
then?
和 P4381 [IOI2008]Island 一样把答案分为两部分。
每个子树内的最长链最大值设为 \(ans1\)
经过环上两个点的最长链设为 \(ans2\) ,难点还是在于求 \(ans2\)
最终答案还是 \(\max(ans1,ans2)\) 真·套路
环是很难搞的,还是拍到序列上。
首先,必然需要选两个端点 \(l,r(l<r)\) ,它们所能达到的最长长度就是 \(f_l+f_r+dis_{l,r}\)
根据套路转化为前缀和之差
??如果认真考虑会发现这里不那么套路了
断了一条边之后有可能 \(sum_r-sum_l\) 或者是 \(sum_n-(sum_r-sum_l)\) 不能达到 ,因为中间有条边断掉了。
那么就分类讨论!
设断的边是 \(i-1\to i\)
- \(l<r\le i-1\) ,记 \(ll_{r}\) 表示这种情况下,右端点为 \(r\) 时直径的最大值。注意这里应该求最大值,因为我们求的是断完边后的直径 。\(ll_{r}=\max\{f_r+sum_r+f_l-sum_l\}\) ,从前往后扫同时维护 \(\max\{f_l-sum_l\}\) 即可求出
- \(i\le l<r\) ,同上,记 \(rr_{l}\) 表示这种情况下,左端点为 \(l\) 时直径的最大值。\(rr_l=\max\{f_r+sum_r+f_l-sum_l\}\) ,从后往前扫同时维护 \(\max\{f_r+sum_r\}\)
- \(l\le i-1,i\le r\) ,那么必然“绕”过了断的边,\(ans=\min\{f_l+sum_l+f_r+sum_{cnt-sum_r} \}\) ,维护 \(L_i=\max\{f_l+sum_l\},R_i=\max\{f_r+sum_{cnt}-sum_r\}\) ,\(ans=\min\{L_{i-1}+R_i\}\)
整合一下就完事了。
我现在对于基环树的感觉就是边界好烦啊,比如这里的 \(dis_{l,r}=sum_r-sum_l\) ,边界加一减一还是不变,我得要输出一下,看看程序跑成啥样才能知道,有没有dalao有好的方法,指点一下蒟蒻吧!
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define x first
#define y second
#define sz(v) (int)v.size()
#define pb(x) push_back(x)
#define mkp(x,y) make_pair(x,y)
inline int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c))x=x*10+c-'0',c=getchar();
return f?x:-x;
}
#define N 200005
#define inf 1000000000000000ll
int n;
int loop[N],cnt,val[N],vis[N];
LL sum[N],f[N],ans1,ans2,L[N],R[N],ll[N],rr[N];
int head[N],num_edge;
struct edge{int nxt,to,val;}e[N<<1];
void addedge(int fr,int to,int val){
++num_edge;
e[num_edge].nxt=head[fr];
e[num_edge].to=to;
e[num_edge].val=val;
head[fr]=num_edge;
}
int dfs(int u,int ft){
if(vis[u])return u;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==ft)continue;
int tmp=dfs(v,u);
if(tmp){
loop[++cnt]=u,val[cnt]=e[i].val,vis[u]=2;
return tmp==u?0:tmp;
}
}
return 0;
}
void getd(int u,int ft){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;if(v==ft||vis[v]==2)continue;
getd(v,u);
ans1=max(ans1,f[v]+f[u]+e[i].val);
f[u]=max(f[u],f[v]+e[i].val);
}
}
signed main(){
n=read();
for(int i=1;i<=n;++i){
int x=read(),y=read(),z=read();
addedge(x,y,z),addedge(y,x,z);
}
dfs(1,0);
for(int i=1;i<=cnt;++i)getd(loop[i],0);
for(int i=1;i<=cnt;++i)sum[i]=sum[i-1]+val[i];
LL tmp;
L[0]=ll[0]=R[cnt+1]=rr[cnt+1]=-inf;
tmp=-inf;
for(int i=1;i<=cnt;++i){
L[i]=max(L[i-1],f[loop[i]]+sum[i]);
ll[i]=max(ll[i-1],f[loop[i]]+sum[i]+tmp);
tmp=max(tmp,f[loop[i]]-sum[i]);
}//ll[r]=max{f[r]+sum[r]+f[l]-sum[l]}
tmp=-inf;
for(int i=cnt;i>=1;--i){
R[i]=max(R[i+1],f[loop[i]]+sum[cnt]-sum[i]);
rr[i]=max(rr[i+1],f[loop[i]]-sum[i]+tmp);
tmp=max(tmp,f[loop[i]]+sum[i]);
}//rr[l]=max{f[r]+sum[r]+f[l]-sum[l]}
//both:ans=min{f[l]+sum[l]+f[r]+sum[cnt]-sum[r]}
ans2=inf;
for(int i=1;i<=cnt;++i)ans2=min(ans2,max(L[i-1]+R[i],max(ll[i-1],rr[i])));
printf("%lld\n",max(ans1,ans2));
return 0;
}
对了,我还从CF上以某种神奇手段把Test#4搞了下来(((
路漫漫其修远兮,吾将上下而求索