BZOJ(本校) 3049 第K大 - 二分&树形dp

【问题描述】
给出一棵N个节点的树,每个节点上有一个正整数权值。给定K。
每条路径都有某个确定的第K大的节点权值,如果路径上点数比K小则规定第K大权值为0。现在有一个问题是,在这棵树里总共O(N^2)条路径,他们所有的第K大节点权值中,最大的那个是多少?
然后还有一个问题。这个问题总共有Q个询问,每次询问格式如下:“假如我把原树中编号为pi的节点权值增加vi,那么最初那个问题的答案会是多少?”规定所有Q个询问中pi都会在给定的某M个节点里面。

【输入格式】
第一行四个数N、K、M、Q。节点从1开始编号。
第二行N个数vi,表示第i个节点的权值。
第三行M个数pi,表示M个选定点。
接下来N-1行每行两个数ui,vi,ui和vi有一条边。
接下来Q行每行两个数pi,vi,表示把节点pi的权值增加vi。这里的pi保证是上面所规定的M个节点里面的一个。

【输出格式】
第一行输出一个数,表示第一问的答案。
接下来Q行每行输出一个数,表示第i个询问的答案。

【输入输出样例1】
6 4 2 0
4 3 5 2 6 1
2 4
1 2
1 3
1 4
1 5
1 6
///
0

【样例解释1】
无论你怎么选择路径,长度最多为3,K=4,所以答案为0。

【输入输出样例2】
12 3 3 5
1 2 3 4 5 6 7 8 9 10 11 12
8 5 7
1 8
8 3
8 11
1 5
5 9
5 2
5 12
1 7
7 4
4 6
4 10
8 1
5 2
7 3
8 4
5 5
///
8
9
8
10
11
10

【样例解释2】
按输出顺序的其中一种选择路径方案如下:11~10;11~10;11~10;12~10;11~12;11~12。
【数据规模】
所有数据均为不超过10^6的正整数。M<=5。
maxN maxK maxQ
1 50 50 50
2 100000 2 100000
3 100 100 100
4 200 200 200
5 300 5 300
6 500 500 100
7~10 100000 100000 100000
注意:本题设置部分分。如果第一行输出正确则得到50%该测试点的分;如果最后的Q行输出均正确则得到另外50%的分。

分析:

  • 第一问:二分+dp判定
    二分最大的第k大的数mid,dp找路径上的点权大于等于mid的最多的点的个数,如果这个个数大于等于k,mid合法;小于k,mid不合法。
  • 第二问:注意到M<=5,且修改不具有后效性。
    找到经过要修改的点x的所有路径,如果x增加权值能改变第一问的ans,那么w[x]<=ans&&w[x]+delta_w>ans,而且新的答案只能用w[x]+delta_w或者以前除了w[x]的第k-1大的元素权值来更新。
  • 总时间复杂度O(M*nlogn)
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100000

struct node{
    int v;
    node *next;
}edge[MAXN*2+10],*adj[MAXN+10],*ecnt=&edge[0];

int n,m,k,ans,b[MAXN+10],w[MAXN+10],f[MAXN+10][2],mx,t,a[MAXN+10],Q;

void addedge(int u,int v)
{
    node *p=++ecnt;
    p->v=v;
    p->next=adj[u];
    adj[u]=p;
}
void read()
{
    int x,y;
    scanf("%d%d%d%d",&n,&k,&m,&Q);
    for(int i=1;i<=n;i++){
        scanf("%d",&w[i]);
        mx=max(mx,w[i]);
    }
    for(int i=1;i<=m;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
}
void DP(int u,int fa,int lmt)
{
    f[u][0]=f[u][1]=0;
    for(node *p=adj[u];p;p=p->next){
        if(p->v==fa) continue;
        DP(p->v,u,lmt);
        if(f[u][0]<=f[p->v][0]){
            f[u][1]=f[u][0];
            f[u][0]=f[p->v][0];
        }
        else if(f[u][1]<f[p->v][0])
            f[u][1]=f[p->v][0];
    }
    if(w[u]>=lmt)
        f[u][0]++;
    f[u][1]+=f[u][0];
    t=max(t,f[u][1]);
}
void Quary1()
{
    int l=1,r=mx,mid;
    while(l<=r){
        mid=(l+r)/2;
        t=0; //一条路径上最大的点权大于mid的点数
        DP(1,0,mid);
        if(t<k)
            r=mid-1;
        else
            l=mid+1,ans=mid;
    }
}
void Quary2() //M*nlogn
{
    for(int i=1,tmp;i<=m;i++){
        tmp=w[a[i]],w[a[i]]=-1;
        int l=1,r=mx,mid;
        while(l<=r){ //找到在经过a[i]的路径中,除了w[a[i]]的第k-1大
            mid=(l+r)/2;
            DP(a[i],0,mid);
            if(f[a[i]][1]<k-1)
                r=mid-1;
            else
                l=mid+1,b[a[i]]=mid;
        }
        w[a[i]]=tmp;
    }
}
int main()
{
    int p,v;
    read();
    Quary1();
    printf("%d\n",ans);
    Quary2();
    while(Q--){
        scanf("%d%d",&p,&v);
        if(w[p]+v<=ans||w[p]>ans) //只有之前小于ans的w加上v之后大于ans,才会可能有答案的更新
            printf("%d\n",ans);
        else
            printf("%d\n",max(ans,min(w[p]+v,b[p]))); //max(原来的第k大,修改后的可能第k大)
    }
}
posted @ 2016-03-07 17:27  KatarinaYuan  阅读(259)  评论(0编辑  收藏  举报