新年礼物综合题DAY2

T1:

题目概述:

设rr是个2^k2k 进制数,并满足以下条件:

(1)r至少是个22位的2^k2k 进制数。

(2)作为2^k2k 进制数,除最后一位外,rr的每一位严格小于它右边相邻的那一位。

(3)将rr转换为22进制数qq后,则qq的总位数不超过ww。

在这里,正整数k(1≤k≤9)k(1≤k≤9)和w(k<W≤30000)w(k<W≤30000)是事先给定的。

问:满足上述条件的不同的r共有多少个?

我们再从另一角度作些解释:设SS是长度为ww 的0101字符串(即字符串SS由ww个“00”或“11”组成),SS对应于上述条件(33)中的qq。将SS从右起划分为若干个长度为kk的段,每段对应一位2^k2k进制的数,如果SS至少可分成22段,则S所对应的二进制数又可以转换为上述的2^k2k进制数rr。

例:设k=3,w=7k=3,w=7。则rr是个八进制数(2^3=823=8)。由于w=7w=7,长度为77的0101字符串按33位一段分,可分为33段(即1,3,31,3,3,左边第一段只有一个二进制位),则满足条件的八进制数有:

22位数:
高位为11:66个(即12,13,14,15,16,1712,13,14,15,16,17),
高位为22:55个,
…,
高位为66:11个(即6767)。
共6+5+…+1=216+5+…+1=21个。

33位数:
高位只能是11,
第22位为22:55个(即123,124,125,126,127123,124,125,126,127),
第22位为33:44个,
…,
第22位为66:11个(即167167)。
共5+4+…+1=155+4+…+1=15个。

所以,满足要求的rr共有3636个。

首先,设一个二维数组,dt[i][j],表示在最高位为i的情况下,有j位的方案数,不难得出,显然,对于每一个数,只要另一个数大于它的上一位就可以发生递推关系,而递推结果为dt[i][j]=dt[i+1][j-1]+xxxx+dt[2^k-i+1][j-1],简而言之,就是f[i][j]=f[i+1][j]+f[i][j-1]。最后对w<=k时的情况进行一次特判就AC了。

T2:

题目描述

公元20442044 年,人类进入了宇宙纪元。

L 国有 nn 个星球,还有 n-1n−1 条双向航道,每条航道建立在两个星球之间,这 n-1n−1 条航道连通了 LL 国的所有星球。

小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u_iui​ 号星球沿最快的宇航路径飞行到 v_ivi​ 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 jj,任意飞船驶过它所花费的时间为 t_jtj​,并且任意两艘飞船之间不会产生任何干扰。

为了鼓励科技创新, LL 国国王同意小 PP 的物流公司参与 LL 国的航道建设,即允许小PP 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

在虫洞的建设完成前小 P 的物流公司就预接了 mm 个运输计划。在虫洞建设完成后,这 mm 个运输计划会同时开始,所有飞船一起出发。当这 mm 个运输计划都完成时,小 PP 的物流公司的阶段性工作就完成了。

如果小 PP 可以自由选择将哪一条航道改造成虫洞, 试求出小 PP 的物流公司完成阶段性工作所需要的最短时间是多少?

输入输出格式

输入格式:

第一行包括两个正整数 n, mn,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 11 到 nn 编号。

接下来 n-1n−1 行描述航道的建设情况,其中第 ii 行包含三个整数 a_i, b_iai​,bi​ 和 t_iti​,表示第 ii 条双向航道修建在 a_iai​ 与 b_ibi​ 两个星球之间,任意飞船驶过它所花费的时间为 t_iti​。数据保证 1 \leq a_i,b_i \leq n1≤ai​,bi​≤n 且 0 \leq t_i \leq 10000≤ti​≤1000。

接下来 mm 行描述运输计划的情况,其中第 jj 行包含两个正整数 u_juj​ 和 v_jvj​,表示第 jj 个运输计划是从 u_juj​ 号星球飞往 v_jvj​号星球。数据保证 1 \leq u_i,v_i \leq n1≤ui​,vi​≤n

输出格式:

一个整数,表示小 PP 的物流公司完成阶段性工作所需要的最短时间。

解答分两个部分:

1、求出给出各点对之间的距离

2、将一条边权减为0使得最大距离最小

树上距离一看就自然想到了【树链剖分+树状数组(或线段树)】,先树剖给点编号,然后套用树状数组结合LCA的算法求出点对距离,1问秒掉

关键是第二问,如何删。

容易想到要选的边一定在最长的路径上,但选最长路径上最长的边不一定是对的,因为第二长的边可能与第一长的边有公共边且不相差多少,但是删去了一个非公共边就错了。

看到最大最小,自然想到二分答案:

我们二分虫洞后最长的边的长度,对于每一个check(m),只需枚举所有比m大的路径,这些路径都得缩短,将这k条路径上每一条边+1,这样一来加到了k的那些边就是所有边的公共边,再看一看他们能不能通过减为0而使这k条边都小于m。

具体怎么维护每条边加了几次,用线段树?

这样二分nlognlogn的复杂度,还是不太放心

鉴于所有的询问都是单点且都在修改之后,我们可以用差分数组以O(n)的总复杂度求出

还是挺优秀的效率

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lbt(x) (x&-x)
using namespace std;
const int maxn=300005,INF=2000000000;
inline int read(){
    int out=0,flag=1;char c=getchar();
    while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
    return out*flag;
}
int N,M,root,rtm=INF,Maxw;
//这块是存边【链式前向星】
int head[maxn],nedge=0;
struct EDGE{
    int to,w,next;
}edge[2*maxn];
inline void build(int a,int b,int w){
    edge[nedge]=(EDGE){b,w,head[a]};
    head[a]=nedge++;
    edge[nedge]=(EDGE){a,w,head[b]};
    head[b]=nedge++;
}
struct node{
    int a,b,w;
}p[maxn];
inline bool operator <(const node& a,const node& b){
    return a.w>b.w;
}
//这块求重心
int Siz[maxn];
void dfs(int u,int f){
    int to,Max=-1,Min=INF;
    Siz[u]=1;
    for(int k=head[u];k!=-1;k=edge[k].next)
        if((to=edge[k].to)!=f){
            dfs(to,u);
            Siz[u]+=Siz[to];
            if(Siz[to]>Max) Max=Siz[to];
            else if(Siz[to]<Min) Min=Siz[to];
        }
    if(N-Siz[u]>Max) Max=N-Siz[u];
    else if(N-Siz[u]<Min) Min=N-Siz[u];
    if(Max!=-1&&Min!=INF&&Max-Min<rtm){
        root=u;
        rtm=Max-Min;
    }
}
//这块是树链剖分
int top[maxn],siz[maxn],fa[maxn],son[maxn],id[maxn],Hash[maxn],dep[maxn],V[maxn],cnt=0;
void dfs1(int u,int f,int d){
    int to;
    siz[u]=1;fa[u]=f;dep[u]=++d;
    for(int k=head[u];k!=-1;k=edge[k].next)
        if((to=edge[k].to)!=f){
            dfs1(to,u,d);
            V[to]=edge[k].w;
            siz[u]+=siz[to];
            if(!son[u]||siz[son[u]]<siz[to]) son[u]=to;
        }
}
void dfs2(int u,int flag){
    int to;
    id[u]=++cnt;Hash[cnt]=u;
    flag ? top[u]=top[fa[u]]:top[u]=u;
    if(son[u]) dfs2(son[u],1);
    for(int k=head[u];k!=-1;k=edge[k].next)
        if((to=edge[k].to)!=son[u]&&to!=fa[u])
            dfs2(to,0);
}
//这块是树状数组
int A[maxn];
inline void add(int u,int v){while(u<=N){A[u]+=v;u+=lbt(u);}}
inline int Sum(int u){int ans=0;while(u>0){ans+=A[u];u-=lbt(u);}return ans;}
inline int Query(int l,int r){return Sum(r)-Sum(l-1);}
inline void init(){for(int i=1;i<=N;i++) add(id[i],V[i]);}
int solve(int u,int v){        //求路径长
    int ans=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        ans+=Query(id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) swap(u,v);
    return ans+Query(id[u]+1,id[v]);
}
int D[maxn];   //差分数组
inline void update(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]) u^=v^=u^=v;
        D[id[top[u]]]+=1;
        D[id[u]+1]-=1;
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]) u^=v^=u^=v;
    D[id[u]+1]+=1;
    D[id[v]+1]-=1;
}
bool check(int m){
    int tot=0,v=0;
    while(p[tot+1].w>m){
        ++tot;
        update(p[tot].a,p[tot].b);
    }
    for(int i=1;i<=N;i++){
        v+=D[i];D[i]=0;
        if(v==tot&&Maxw-V[Hash[i]]<=m){
            for(int j=i+1;j<=N;j++) D[j]=0;
            return true;
        }
    }
    return false;
}
int main()
{
    fill(head,head+maxn,-1);
    N=read();
    M=read();
    int a,b,w,L=0,R=0;
    for(int i=1;i<N;i++){
        a=read();
        b=read();
        w=read();
        build(a,b,w);
    }
    dfs(1,0);     //求出重心作为根
    dfs1(root,0,0);  //dfs1、dfs2树链剖分
    dfs2(root,0);
    init();      //初始化树状数组
    for(int i=1;i<=M;i++){
        p[i].a=read();
        p[i].b=read();
        p[i].w=solve(p[i].a,p[i].b);
        if(p[i].w>R) R=p[i].w;
    }
    sort(p+1,p+1+M);  //路径排个序
    Maxw=R;
    while(L<R){   //二分答案
        int mid=(L+R)>>1;
        if(check(mid)) R=mid;
        else L=mid+1;
    }
    cout<<L<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/PUBG___/article/details/85860050