图论训练之十三
https://www.luogu.org/problem/P5021
好早以前做过,但现在再看发现当时根本没理解
分析:
题目大意:
找出互不相交的m条路径,使得这m条路径中最短的尽量最长(原图是一颗树)!!!
最长的最短:二分答案?长度大于等于mid的有没有m个
树?树?树?树形dp?
当我们二分这个答案后,关键就在于如何判断能否组成m条路径
这真的很棘手,我以前从来没遇到过找路径的
关键就在于这是颗树
树!树!树!
考虑当前节点u与其子树v1,v2,v3...所连接的边(u,v1),(u,v2),(u,v3)....
发现经过这些边的路径至多只能是一条!!!!
但还要先满足<=mid的限制条件
剩余的边怎么办?就两两组合
但两两要最优组合
怎么最优组合?
尽量两两组合后越靠近mid就越好
非multiset的一个二分解决
multiset的一个lower_bound解决
将失配的上传
非multiset code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5, MAXM = MAXN << 1;
const int INF = 0x7fffffff;
int head[MAXM], nxt[MAXM], v[MAXM], w[MAXM], cnt;
int n, m;
int root;
inline void Addline(int x, int y, int z)
{
v[cnt] = y, w[cnt] = z;
nxt[cnt] = head[x], head[x] = cnt++;
return;
}
int dp[MAXN],tag[MAXN];//dp是向上贡献的最长链长度
int que[MAXN], tail;
int res;
inline void DFS(int x, int from, int lim)
{
for (int i = head[x]; ~i; i = nxt[i])
if (v[i] != from)
DFS(v[i], x, lim);
tail = 0;
for (int i = head[x]; ~i; i = nxt[i])if (v[i] != from)que[++tail] = dp[v[i]] + w[i];
sort(que + 1, que + tail + 1);
for (int i = tail; i >= 1 && que[i] >= lim; i--)tail--, res--;
for (int i = 1; i <= tail; i++)
if (tag[i] != x)
{
int l = i + 1, r = tail, pst = tail + 1;
while (l <= r)
{int mid=l+r>>1;
if (que[i] + que[mid] >= lim)
pst = mid, r = mid - 1;
else
l = mid + 1;
}
while (tag[pst] == x && pst <= tail)pst++;
if (pst <= tail)
tag[i] = tag[pst] = x, res--;
}
dp[x] = 0;
for (int i = tail; i >= 1; i--)
if (tag[i] != x){dp[x] = que[i];break;}
return;
}
signed main(void)
{
memset(head, -1, sizeof head);
cin >> n >> m;
for (int i = 1, x, y, z; i < n; i++)
{
scanf("%d %d %d", &x, &y, &z);
Addline(x, y, z), Addline(y, x, z);
}
root = rand() % n + 1;
int l = 0, r = INF, ans = 0;
while (l <= r)
{int mid=l+r>>1;
res = m;
memset(tag, false, sizeof tag);
DFS(root, 0, mid);
if (res <= 0)
ans = mid, l = mid + 1;
else
r = mid - 1;
}
cout << ans << endl;
return 0;
}
mutiset code:
#include<bits/stdc++.h>
using namespace std;
int cnt,fst[50005],nxt[100005],to[100005],w[100005];
int n,m,l=2147400000,r,mid,ans,f[50005],g[50005];
multiset <int> s;
multiset <int>::iterator it;
void AddEdge(int u,int v,int c)//链式前向星
{
to[++cnt]=v;
nxt[cnt]=fst[u];
fst[u]=cnt;
w[cnt]=c;
}
void Dfs(int u,int faz)//树形DP部分
{
for(int i=fst[u];i;i=nxt[i])
{
int v=to[i];
if(v==faz) continue;
Dfs(v,u);
f[u]+=f[v];
}
for(int i=fst[u];i;i=nxt[i])
{
int v=to[i];
if(v==faz) continue;
s.insert(g[v]+w[i]);
}
while(!s.empty())
{
int now=*s.rbegin();
if(now>=mid)
{
it=s.find(now);
f[u]++;
s.erase(it);
}
else break;
}
while(!s.empty())
{
int now=*s.begin();
s.erase(s.begin());
it=s.lower_bound(mid-now);//找不到就返回end()
if(it==s.end()) g[u]=now;
else
{
f[u]++;
s.erase(it);
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
AddEdge(x,y,z);
AddEdge(y,x,z);
l=min(l,z);
r+=z;
}
while(l<=r)//二分答案
{
mid=l+r>>1;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
Dfs(1,0);
if(f[1]>=m)
{
ans=max(ans,mid);
l=mid+1;
}
else r=mid-1;
}
printf("%d",ans);
return 0;
}