noip 2018 D1T3 賽道修建
首先考慮二分答案,這時需要的就是對於一個長度求出能在樹中選出來的最多的路徑條數。考慮到一條路徑是由一條向上的路徑與一條向下的路徑構成,或者僅僅是向上或向下的路徑構成。
設\(f_i\)為i這顆子樹中最多能選出來多少條路徑,\(g_i\)為在i這顆子樹內選出來\(f_i\)條路徑后最多能往下延伸多么長的距離,就是以i點為端點向i的子樹內可以選出來的最長的路徑。
考慮一顆以i為根的子樹,首先\(f_i=\sum_{j\in the\ son\ of\ i}f_j\),然后對i的所有兒子的\(g_j+w_{i,j}\)排序,如果該值大於二分的答案,則直接將這個看成一條單獨的路徑一定不會更劣,那么直接將\(f_i\)加一。
然后從小到大枚舉這個值,找到另一個最小的未被使用過的值使得兩個數相加大於答案,並刪掉這兩個值。如果找不到這樣的值,則用枚舉的這個值更新\(g_i\)。
實現的時候可以用multiset,時間復雜度大概是\(O(nlog^2n)?\)
但是我的常數好大啊,在洛谷上開個O2就過了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
const int Maxn=110000;
int to[Maxn],w[Maxn],nxt[Maxn],first[Maxn],f[Maxn],g[Maxn],tot=1;
int n,m,mid,u,v,wi;
inline void add(int u,int v,int wi) {
to[tot]=v;
w[tot]=wi;
nxt[tot]=first[u];
first[u]=tot++;
to[tot]=u;
w[tot]=wi;
nxt[tot]=first[v];
first[v]=tot++;
}
void work(int root,int fa) {
f[root]=g[root]=0;
multiset<int>se;
vector<int>vi;
for(int i=first[root];i;i=nxt[i])
if(to[i]!=fa) {
work(to[i],root);
int temp=g[to[i]]+w[i];
f[root]+=f[to[i]];
if(temp>=mid) {
f[root]++;
continue;
}
vi.push_back(temp);
se.insert(temp);
}
sort(vi.begin(),vi.end());
for(vector<int>::iterator i=vi.begin();i!=vi.end();i++)
if(se.count(*i)) {
se.erase(se.find(*i));
multiset<int>::iterator j=se.lower_bound(mid-*i);
if(j==se.end())
g[root]=*i;
else {
f[root]++;
se.erase(j);
}
}
}
int main() {
// freopen("test.in","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++) {
scanf("%d%d%d",&u,&v,&wi);
add(u,v,wi);
}
int l=1,r=0x3f3f3f3f;mid=100000;int ans=1;
work(1,1);
if(f[1]<m) r=99999;
else l=100000,ans=100000;
while(l<=r) {
// memset(f,0,sizeof(f));
// memset(g,0,sizeof(g));
work(1,1);
if(f[1]>=m) {
ans=mid;
l=mid+1;
}
else r=mid-1;
mid=l+r>>1;
}
printf("%d\n",ans);
return 0;
}