[TJOI2017]城市
题意:
一棵树,现在要求你将一条边改变他的位置,(即改变左右所连接的端点,权值不变),修改后任意两点相互可达,且使得两个点之间的最大交通费用最小
题解:
有O(n^2)和O(n)两种做法
参考题解:
参考1
参考2
再有研究分析半小时后,我会了O(n^2)的做法:
我们可以暴力删除一个边,O(n)
然后处理最远点对的问题O(n)
这个最远点对怎么考虑?
当删除一个边后,整棵树就被分成两个联通块,此时的最长路由三种来源:
- 最远点对都在连通块1
- 最远点对都在连通块2
- 最远点对分别在连通块1和2
前两种情况本质就是分别求树的直径,拿第三种情况呢?我们设最远点对分别是u,v
u在连通块1,v在2
那么答案就是u到连通块1任意一点的最长距离+val(当初被删的边的边权)+v在连通块2任意一点的最长距离
最终答案是要求这三种情况的最大值尽可能小,而前两种情况是固定的(在删完边之后就是固定的),所有我们就要让第三种情况中两个点的到自己联通块的最长距离最小。这就是最优策略
具体做法:
我们一边做树形dp,一边求出对于每一个子树,求出经过它的最长链和次长链。直径就是枚举每个点,求max(最长链+次长链)
而我们要求点x在连通块内的最远距离(另一端为y),
y有可能在x的子树内,那len(x,y)就是上面求得最长链。
y如果在子树外,len(x,y)就等于x到父亲fa的距离+from
from有两个来源:
1.在父亲子树之外的最长链
2.在父亲子树之内的最长链
对于第2种情况,如果x在就在父亲子树的最长链上,那么我们就要取次长链,否则就是最长链
?这句话什么意思?我们是通过换根操作,用父亲子树的最长链来求x的最长链,如果x在父亲子树的最长链,换根就会影响最长链,所以要用次长链
通过下图理解理解
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5010;const int INF=0x3f3f3f3f;
struct data{int v;int nxt;int val;}edge[2*N];
int alist[N];int cnt;int n;
inline void add(int u,int v,int val)
{edge[++cnt].v=v;edge[cnt].nxt=alist[u];alist[u]=cnt;edge[cnt].val=val;}
int dp[N];int mv[N];int nxdp[N];bool book[N];int rad=INF;int dis;int res=INF;
void getd(int x)//求直径
{book[x]=true;int nxt=alist[x];while(nxt){int v=edge[nxt].v;int val=edge[nxt].val;if(!book[v]){getd(v);int va=dp[v]+val;//最长链和次长链 if(va>dp[x]){//如果大于最大的 nxdp[x]=dp[x];dp[x]=va;mv[x]=v;//在最长链上,x的下一位是v }else if(va>nxdp[x]){//大于第二大 nxdp[x]=va;}}nxt=edge[nxt].nxt;}dis=max(dis,dp[x]+nxdp[x]);//更新直径
}
void getr(int x,int fr)
{rad=min(rad,max(fr,dp[x]));//更新半径 book[x]=false;int nxt=alist[x];while(nxt){int v=edge[nxt].v;int val=edge[nxt].val;if(book[v]){if(v==mv[x]){//如果在最长链 getr(v,max(nxdp[x]+val,fr+val));// nxdp[x]+val 就是次长链+val //fr 他父亲的from }else { getr(v,max(dp[x]+val,fr+val));//否则就是最长链+val }}nxt=edge[nxt].nxt;}
}
inline void clear(){//清空dp的函数 for(int i=1;i<=n;i++){dp[i]=mv[i]=nxdp[i]=book[i]=0;}rad=INF;dis=0;
}
int u[N];int v[N];int val[N];
int main()
{scanf("%d",&n);for(int i=1;i<n;i++)//读进来 {scanf("%d%d%d",&u[i],&v[i],&val[i]);add(u[i],v[i],val[i]);add(v[i],u[i],val[i]);}for(int i=1;i<n;i++)//枚举边 {int d1;int d2;int r1;int r2;book[v[i]]=1;getd(u[i]);d1=dis;//求直径 dis=0;getd(v[i]);d2=dis;//求直径 book[v[i]]=0;getr(u[i],0);r1=rad;//求半径 rad=INF;getr(v[i],0);r2=rad;//求半径 res=min(res,max(max(d1,d2),r1+r2+val[i]));//更新答案 clear();//不要忘记清空 }printf("%d",res);return 0;//拜拜程序~
}