并不对劲的uoj276. [清华集训2016]汽水

想要很对劲的讲解,请点击这里

题目大意

有一棵\(n\)(\(n\leq 50000\))个节点的树,有边权
求一条路径使该路径的边权平均值最接近给出的一个数\(k\)
输出边权平均值下取整的整数部分

题解

有关路径的边权平均值可以二分来做
假设已二分出边权平均值与\(k\)的差不超过\(m\),则应存在一条简单路径,路径上的\(p\)条边满足:
\[-m\leq\frac{\sum_{i=1}^{p}{w_i}}{p}-k\leq m\]
两边同乘\(p\),得:
\[-m*p\leq\sum_{i=1}^{p}{w_i-k}\leq m*p\]
也就是\[\begin{cases}{\sum_{i=1}^{p}{(w_i-k+m)}}\geq 0\\\sum_{i=1}^{p}{(w_i-k-m)}\leq 0\end{cases}\]
那么设\(a_i=w_i-k+m,b_i=w_i-k-m\),问题就变成了判断图中是否存在一条简单路径,满足\(\Sigma a_i\geq 0\)\(\Sigma b_i\leq 0\)
这种在树上找满足某个条件的链的问题可以用点分治解决
设某点\(i\)到当前区域的重心的简单上,\(a_k\)之和为\(A_i\)\(b_k\)之和为\(B_i\)
则问题转化为找两点\(i\)\(j\)不属于当前区域的重心的同一子树,且\(A_i+A_j\geq0\)\(B_i+B_j\leq0\)
也可以看成是对于点\(i\),所有\(A_j\geq-A_i\)的点\(j\)中,\(B_j\)的最小值是否小于\(-B_i\)
这个可以用平衡树或权值线段树来维护
再算上二分、点分治的时间复杂度,总时间复杂度使\(\Theta(n\space log^3 n)\)的,看上去有点悬
考虑加一些小优化,比如:
1.预处理点分树,就不用二分时每次判断都求一遍重心;
2.当找到一条合法的链后,立刻退出;
3.没有必要把当前区域的重心的最后一个儿子的子树里的点的信息加入平衡树或权值线段树
...
然而还是跑得很慢…

#include<algorithm>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define maxn 50010
#define maxm (maxn<<1)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(int i=(x);i>=(y);--i)
#define ls son[u][0]
#define rs son[u][1]
#define eps 5e-3
#define LD long double
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)&&ch!='-')ch=getchar();
    if(ch=='-')ch=getchar(),f=-1;
    while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return x*f;
}
const double inf=(1.0/0.0);
double mx[maxn],key[maxn],key2[maxn],dep[maxn],dep2[maxn],w[maxm],K,L,R,mid,ans;
int n,fa[maxn],son[maxn][2],rt,vis[maxn],ww[maxn],siz[maxn],nowsiz,mnsiz,wt,nxt[maxm],fir[maxn],v[maxm],cnt,nd,yes;
int nt[maxn],vv[maxn],fs[maxn],cnt2,root;
void ade(int u1,int v1,double w1){v[cnt]=v1,nxt[cnt]=fir[u1],w[cnt]=w1,fir[u1]=cnt++;}
void ade2(int u1,int v1){vv[cnt2]=v1,nt[cnt2]=fs[u1],fs[u1]=cnt2++;}
void getsiz(int u,int fa)
{
    siz[u]=1;int nowmx=0;
    for(int k=fir[u];k!=-1;k=nxt[k])
        if(v[k]!=fa&&!vis[v[k]])getsiz(v[k],u),siz[u]+=siz[v[k]],nowmx=max(nowmx,siz[v[k]]);
    nowmx=max(nowmx,nowsiz-siz[u]);
    if(nowmx<mnsiz)mnsiz=nowmx,wt=u;
}
void getwt(int u,int fa,int sumsiz)
{
    nowsiz=sumsiz,mnsiz=n+1,getsiz(u,0),vis[wt]=1;
    int now=wt;
    if(sumsiz==n)root=now;
    else ade2(fa,now);
    for(int k=fir[now];k!=-1;k=nxt[k])
        if(!vis[v[k]])getwt(v[k],now,siz[v[k]]>siz[now]?sumsiz-siz[now]:siz[v[k]]);
}
void pu(int u)
{
    if(!u)return;
    mx[u]=key2[u];
    if(ls)mx[u]=min(mx[u],mx[ls]);
    if(rs)mx[u]=min(mx[u],mx[rs]);
    return;
}
int getso(int u){return son[fa[u]][0]!=u;}
void rot(int u)
{
    register int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),r=l^1,rson=son[u][r];
    if(ffu)son[ffu][fl]=u;son[u][r]=fu,son[fu][l]=rson;if(rson)fa[rson]=fu;fa[u]=ffu,fa[fu]=u;
    pu(fu),pu(u);
}
void ins(double x,double y)
{
    register int u=rt,lst=0,to;
    while(u&&key[u]!=x)lst=u,to=x>key[u]?1:0,u=son[u][to];  
    if(!u)
    {
        u=++nd,ls=rs=0,key[u]=x,key2[u]=y,mx[u]=y,ww[u]=rand()*rand()%63427919,fa[u]=lst;
        if(lst)son[lst][to]=u/*,upd2(lst)*/;
        else rt=u; 
        while(fa[u]&&ww[fa[u]]>ww[u])rot(u);
        if(!fa[u])rt=u;
    }
    else {key2[u]=min(key2[u],y),mx[u]=min(mx[u],y);while(u)pu(u),u=fa[u];}return;
}
double ask(double x)
{
    int u=rt;double res=inf;
    while(u)
    {
        if(x<=key[u]){res=min(res,min(key2[u],rs?mx[rs]:inf));u=ls;}
        else u=rs;
    }
    return res;
}
void asky(int u,int fa)
{
    double res=ask(-dep[u]);
    if(res!=inf){if(res+dep2[u]<=0){yes=1;return;}}
    for(int k=fir[u];k!=-1;k=nxt[k])
        if(!vis[v[k]]&&v[k]!=fa)
        {
            dep[v[k]]=dep[u]+w[k]+mid,dep2[v[k]]=dep2[u]+w[k]-mid,asky(v[k],u);
            if(yes)return;
        }
}
void addy(int u,int fa)
{
    ins(dep[u],dep2[u]);
    for(int k=fir[u];k!=-1;k=nxt[k])
        if(!vis[v[k]]&&v[k]!=fa)addy(v[k],u);
}
void reset(){rt=0;nd=0,mx[0]=inf;}
void gety(int now)
{
    reset();
    ins(0,0);
    vis[now]=1;
    int lst=0;
    for(int k=fir[now];k!=-1;k=nxt[k])if(!vis[v[k]])lst=v[k];
    for(int k=fir[now];k!=-1;k=nxt[k])
    {
        if(!vis[v[k]])
        {
            dep[v[k]]=w[k]+mid,dep2[v[k]]=w[k]-mid;
            asky(v[k],now);
            if(yes)return;
            if(v[k]!=lst)addy(v[k],now);
        }
    }
    for(int k=fs[now];k!=-1;k=nt[k])
        {gety(vv[k]);if(yes)return;}
}
int main()
{
    srand(time(0));
    nowsiz=n=read();scanf("%lf",&K);
    memset(fir,-1,sizeof(fir));
    memset(fs,-1,sizeof(fs));
    rep(i,1,n-1){int x=read(),y=read();double z;scanf("%lf",&z);R=max(R,z-K<0.0?K-z:z-K),ade(x,y,z-K),ade(y,x,z-K);}
    getwt(1,0,n);
    ans=R;
    while(R-L>=eps)
    {
        mid=(R+L)/2.0;yes=0;
        rep(i,1,n)vis[i]=0;
        gety(root);
        if(yes)ans=min(ans,mid),R=mid;
        else L=mid;
    }
    printf("%.0lf",floor(ans));
    return 0;
}

转载于:https://www.cnblogs.com/xzyf/p/10059681.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
.condarc文件可以用来配置Anaconda的源和其他设置。如果不想使用清华源,可以将清华源的URL从.condarc文件中删除或注释掉。具体来说,可以按照以下步骤进行操作: 1. 打开.condarc文件,该文件通常存储在C:\Users\xxxx.conda\路径中(Windows系统)。 2. 在文件中找到channels部分,该部分列出了Anaconda的源。清华源的URL通常以https://mirrors.tuna.tsinghua.edu.cn开头。 3. 将清华源的URL从channels部分中删除或注释掉(在URL前面添加#符号)。 4. 保存并关闭.condarc文件。 这样,Anaconda将不再使用清华源作为默认源,而会使用其他默认源或您在文件中指定的其他源。请注意,如果您删除了所有源,Anaconda将无法下载和安装软件包。因此,确保至少保留一个可用的源。 #### 引用[.reference_title] - *1* [.condarc源文件存储位置以及Ubuntu换源](https://blog.csdn.net/qq_39213284/article/details/113981429)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【Anaconda实用教程】Anaconda使用修改.condarc文件和常用命令、及解决没有.condarc的情况](https://blog.csdn.net/weixin_51484460/article/details/130449573)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值