您的位置:首页 > 其它

hdu4679 Terrorist’s destroy 解题报告

2013-08-18 15:50 267 查看
题意:给你一棵树和边上的权值,定义去掉一条边的花费为边权值(a)乘上b,b定义为去掉边后形成的两棵树中两点间的最远距离(注意是各自内部的最远距离),问去掉哪条边的花费最少?

分析:首先我们可以将树的直径找出来,如下图



假设1->6就是这棵树的直径,非直径上的边为枝条,有两种删除边的方式:

1.去掉的是枝条

2.去掉的是直径上的边

方式1:b值一定是直径长度,可以枚举直接处理。

方式2:我们先把直径上的点做预处理,dep[i]表示第i个点的最深的枝条深度(如dep[1] = 1; dep[2] = 2; dep[3] = 3; dep[4] = 1; dep[5] = 2;dep[6] = 1)

f1[i] 表示删除第i个点右边的边左侧能得到的b值 f2[i] 表示删除第i个点左边的边右侧能得到的b值

(如f1[1] = 0; f1[2] = 2; f1[3] = 4; f1[4] =4; f1[5] =5; ) 那么f1 ,f2的值怎么求呢? 可以证明f1的值只能是(一部分直径+一条枝条的长度)或是一部分直径的长度,绝不可能是由两段枝条组成。

所以我们有递推式 f1[i] = max{f1[i-1],i-1+dep[i]-1} 注意:这里的i是指从左边数第几个点,并非编号

f2递推式类似,有了f1和f2数组我们就可以枚举直径上的边了,从而在O(1)的时间算得每次的花费

c++代码

#include<cstdio>
#include<cstring>
const int INF = 2000000000;
const int MAXX = 200020;
int q[MAXX],dislong[MAXX],f1[MAXX],f2[MAXX],pre[MAXX],father[MAXX],dep[MAXX];
int list[MAXX],next[MAXX],p[MAXX],c[MAXX],order[MAXX],maxx,n;
bool b[MAXX],inlong[MAXX];

int getmax(int x,int y)
{
return x>y?x:y;
}
int getmin(int x,int y)
{
return x>y?y:x;
}
int findlong(int xx,int n)
{
int t,w,now,k,x;
for (int i = 1; i <= n; i++)
{
b[i] = true;
}
t = 0; w = 1;
q[1] = xx;
dislong[1] = 1;
b[xx] = false;
pre[xx] = 0;
maxx = 0;
while (t < w)
{
t++; x = q[t];
k = list[x];
while (k > 0)
{
if (b[p[k]] == true)
{
w++;
b[p[k]] = false;
q[w] = p[k];
dislong[w] = dislong[t]+1;
pre[p[k]] = x;
if (dislong[w] > maxx) {maxx = dislong[w]; now = p[k];}
}
k = next[k];
}
}
return now;
}
void init(int n)
{
for (int i = 1; i <= n; i++)
{
list[i] = 0;
}
}
void dfs(int x,int pre1)
{
int k;
dep[x] = 1;
k = list[x];
while (k > 0)
{
if (inlong[p[k]] == false && p[k] != pre1)
{
dfs(p[k],x);
dep[x] = getmax(dep[x],dep[p[k]]+1);
}
k = next[k];
}
}
void finddep()
{
int i;
for (i = 1; i <= n; i++)
if (inlong[i])
{

dfs(i,0);
}
}
int main()
{
int step,temp,kase,tot,i,k,kk,x,y,z,front,rear;
int sum,ans,result;
scanf("%d",&temp);
for (kase = 1; kase <= temp; kase++)
{
tot = 0;
scanf("%d",&n);
init(n);
for (i = 1;i < n; i++)
{
scanf("%d%d%d",&x,&y,&z);
tot++;
next[tot] = list[x];
list[x] = tot;
p[tot] = y;
c[tot] = z;
order[tot] = i;
tot++;
next[tot] = list[y];
list[y] = tot;
p[tot] = x;
c[tot] = z;
order[tot] = i;
}
if (n != 1)
{
memset(pre,0,sizeof(pre));
front = findlong(1,n);
rear = findlong(front,n);
sum = maxx-1;

k = rear;
memset(inlong,false,sizeof(inlong));
memset(father,0,sizeof(father));
while (k > 0)
{
inlong[k] = true;
father[pre[k]] = k;
k = pre[k];
}
memset(dep,0,sizeof(dep));
finddep();//
//cong front -> rear
memset(f1,0,sizeof(f1));
memset(f2,0,sizeof(f2));
k = front; step = 0;
while (k != rear)
{
step++;
f1[k] = getmax(f1[pre[k]],step-1+dep[k]-1); //?
k = father[k];
}
// cong rear->front
k = rear; step = 0;
father[rear] = 0;
while (k != front)
{
step++;
f2[k] = getmax(f2[father[k]],step-1+dep[k]-1);
k = pre[k];
}
//遍历直径
ans = INF;
result = INF;
k = front;
while (k != rear)
{
kk = list[k];
while (kk > 0)
{
if (p[kk] == father[k]) break;
kk = next[kk];
}
if (ans > c[kk]*getmax(f1[k],f2[father[k]]))
{
ans = c[kk]*getmax(f1[k],f2[father[k]]);
result = order[kk];

}
else if (ans == c[kk]*getmax(f1[k],f2[father[k]]))
{
if (result > order[kk]) result = order[kk];
}
k = father[k];
}

//遍历枝条
for (i = 1; i <= n; i++)
{
k = list[i];
while (k > 0)
{
if(!(inlong[i] && inlong[p[k]]))
{
if (ans > c[k]*sum)
{
ans = c[k]*sum;
result = order[k];

}
else if (ans == c[k]*sum)
{
if (result > order[k]) result = order[k];
}
}
k = next[k];
}
}
printf("Case #%d: ",kase);
printf("%d\n",result);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: