当前位置: 代码迷 >> 综合 >> 洛谷P1084 NOIP2012 二分+set
  详细解决方案

洛谷P1084 NOIP2012 二分+set

热度:82   发布时间:2023-12-14 10:08:17.0

题面

提供一种可能比较简单的二分+setsetset做法。

\quad 大体思路: 因为用更多的时间肯定也可以完成要求,
所以满足单调性,所以考虑二分答案。对于每次的答案midmidmid,把所有
军队在midmidmid的时间内尽量往上提,可以用倍增在单次O(logn)O(logn)O(logn)的时间里做到。如果能到达根节点,往setsetset中插入该军队到根节点后还能移动的距离(下文称之为可移动距离),这些军队可以进驻在自身的可移动距离内的任意节点。

\quad所有节点都尽量向上移动后,统计根节点各个子节点是否需要军队驻扎。如果某个子节点aaa没有军队上到根节点且需要军队,那么直接在set中找到可移动距离尽量小但能到达aaa的军队驻扎并在setsetset中删除该军队。

\quad如果某个节点bbb有军队到了根节点但是需要驻扎,我们设根到该节点距离为www,该点往setsetset中插入的的军队的可移动距离为vvv(有多个取较小值),我们讨论一下:

  • w>vw>vw>v,如果用根节点储存的军队驻扎,就是用大于www的值变成vvv明显不划算,所以我们直接"撤回",让这个节点的军队退回去,在set中删除该军队。

  • w<vw<vw<v,用setsetset储存的军队驻扎,可能使用的军队的可移动距离<v<v<v,这样我们是賺了的,而用大于vvv的也不会更劣(因为vvv已经插入setsetset中了,如果当前≥v\ge vv,说明这个军队在进驻了其它节点,如果让这个军队回来就要让可移动距离更大的军队去原来这个军队进驻的点,不会让结果更优),那么我们就用setsetset中的可移动距离最小且能移动到bbb的军队驻扎在bbb点。

\quad如果所有根节点子节点都有驻扎,那么这个midmidmid是合法的。总的时间复杂度是O(nlog2n)O(nlog^2n)O(nlog2n)。常数略大,仅供娱乐。

另外如果军队数小于根节点的子节点数,那么无解,输出-1。

代码&注释

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
#define MN 50005
#define INF 1e16
#define Lg 17
#define LL long long
#define itset multiset<LL>::iterator
inline int max(int a,int b){
    if(a>b)return a;return b;}//提醒一下,三目运算符速度比if……else快,但是没有1个if快
inline int read(){
    int a=0;char c=getchar();while(c>57 or c<48)c=getchar();while(47<c and c<58){
    a=a*10+c-48;c=getchar();}return a;
}
struct Edge{
    int to,v;Edge(int TO,int V){
    to=TO;v=V;}
};//vector建边的结构体
vector<Edge>edge[MN];
multiset<LL>cnt;//记录根节点储存的节点
int fa[Lg][MN],tag[MN],n,m,u,v,w,loc[MN];
LL UP[MN],cost[Lg][MN],lon[MN];
//fa、cost:倍增的父亲数组和花费的时间
//loc:军队初始位置
//tag:这个节点是否有军队
//UP:从这个节点上到根节点的军队的可移动距离
//lon:一棵树的最长链
void dfs(int x){
    for(int i=1;i<Lg;++i){
    fa[i][x]=fa[i-1][fa[i-1][x]];cost[i][x]=cost[i-1][x]+cost[i-1][fa[i-1][x]];}//倍增预处理for(int i=0;i<edge[x].size();++i)if(edge[x][i].to!=fa[0][x]){
    fa[0][edge[x][i].to]=x;cost[0][edge[x][i].to]=edge[x][i].v;dfs(edge[x][i].to);lon[x]=max(lon[x],lon[edge[x][i].to]+(LL)edge[x][i].v);}
}
void up(int x,LL T){
    //尽量向上跳int tmp=x;for(int i=Lg-1;i>=0;--i)if(cost[i][x]<=T&&fa[i][x]>1){
    //要记录由哪个节点跳到1,所以先不能跳到1T-=cost[i][x];x=fa[i][x];//注意这里!先减花费的时间再跳向父亲!}if(T<cost[0][x]){
    tag[x]=1;return;}//跳不了了,打个标记UP[x]=min(UP[x],T-cost[0][x]);//记录由这个节点跳上去的军队的最小的可移动距离cnt.insert(T-cost[0][x]);//插入该军队的可移动距离
}
void DFS(int x){
    if(tag[x])return;//该节点有军队,不用进驻了if(edge[x].size()!=1)tag[x]=1;//size等于1就是叶子节点for(int i=0;i<edge[x].size();++i)if(edge[x][i].to!=fa[0][x]){
    DFS(edge[x][i].to);if(!tag[edge[x][i].to]){
    tag[x]=0;//子节点都有军队,那么也不用进驻了}}
}
bool chk(LL T){
    cnt.clear();memset(tag,0,sizeof(tag));memset(UP,0x7f7f7f,sizeof(UP));for(int i=1;i<=m;++i){
    up(loc[i],T);//跳}DFS(1);//统计for(int i=0;i<edge[1].size();++i){
    if(!tag[edge[1][i].to]){
    if(UP[edge[1][i].to]<edge[1][i].v){
    cnt.erase(cnt.find(UP[edge[1][i].to]));tag[edge[1][i].to]=1;}}//直接把这个军队撤回来}for(int i=0;i<edge[1].size();++i){
    if(!tag[edge[1][i].to]){
    itset IT=cnt.lower_bound(edge[1][i].v);	if(IT==cnt.end()) return 0;//找不到符合要求的军队,不合法cnt.erase(IT);}	//上下2个操作不能同时进行,不然可能把需要撤回的军队拿去驻扎了,比较麻烦}return 1;
}
signed main(){
    n=read();	for(int i=1;i<n;++i){
    u=read();v=read();w=read();edge[u].push_back(Edge(v,w));edge[v].push_back(Edge(u,w));//建边}dfs(1);m=read();for(int i=1;i<=m;++i)loc[i]=read();LL l=-1;LL r=lon[1]<<1;//最大需要的时间是最长链的2倍while(l+1<r){
    LL mid=(l+r)>>1;if(chk(mid)) r=mid;else l=mid;}//左开右闭的奇葩二分小心品尝。。。printf("%lld",r);return 0;
}