NKOJ P3004 【CQ Training 2014 Day1】第K大
NKOJ P3004 【CQ Training 2014 Day1】第K大
《关于太懒所以直接把原题面截图截下来扔上去这档事》
题目描述
输入格式
输出格式
样例输入1
6 4 2 0
4 3 5 2 6 1
2 4
1 2
1 3
1 4
1 5
1 6
样例输出1
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
样例输出2
8
9
8
10
11
10
核心:点分治,二分答案
第一问:
所有的第K大问题基本上属于两个类别:二分答案、堆。这道题初步看来用堆实现会爆时间复杂度(即存下前K大的数),所以直接二分答案搞定。每次二分出一个值,判断路径上是否有\(>=K\)个比它大的数。如果存在则向上二分,否则向下。
再看到这是一棵树,所以点分这个暴力呼之欲出。每次找到当前子树的最长链,跟之前子树的最长链相加就能求出总的最长路径。
第二问:
在\(K!=1\)的前提下,分三种情况讨论。(此处设第一问答案为\(ans0\))
1.\(ans0 \leq V_{p_i}\),则此次增加不会对原来的第K大造成影响(因为已经比第\(K\)大还要靠前了),所以直接输出\(ans0\)即可。
2.\(V_{p_i}+v_i \leq ans0\),同上,由于即使加权排名依然比\(K\)靠后,所以直接输出\(ans0\)即可。
3.$ans0\leq V_{p_i}+v_i && V_{p_i} \leq ans0 \(,此时的加权(可能)会对原答案造成影响。由于\)p_i\(排名的上升(可能\)p_i\(就是它所在的链的第\)K\(大),它所在链的第\)K-1\(大可能会成为新的第\)K\(大。所以先取出\)min(V_{p_i}+v_i,K-1\(大\))\(,再输出取出的结果和\)ans0$中更大的那个即可。
注:由于是固定求最长的链,应该可以用\(D_aF_aS_{hi}\)搞定。但是我太弱了不会
KONO代码哒!
#include<cstdio>
#include<cmath>
using namespace std;
char *p1,*p2,buf[1<<20];
//#define GC (p1==p2&&(p1=buf,p2=buf+fread(buf,1,1<<20,stdin),p1==p2)?0:(*(p1++)))
#define GC getchar()
inline int in()
{
int ans;
char t,k;
while(((t=GC)!='-'&&(t>'9'||t<'0')));
k=(t=='-');
ans=k?0:(t-'0');
while((t=GC)>='0'&&t<='9')ans=ans*10+t-'0';
return k?-ans:ans;
}
const int maxn=100010;
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x>y?y:x;}
struct edge{
int next,to;
}g[maxn<<1];
int head[maxn],cnt;
void add(int from,int to)
{
g[++cnt].next=head[from];
g[cnt].to=to;
head[from]=cnt;
}
int G;
int size[maxn];
bool mark[maxn];
void f_g(int x,int f,int now)
{
int _max=0;
size[x]=1;
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v]&&v!=f)
{
f_g(v,x,now);
size[x]+=size[v];
_max=max(_max,size[v]);
}
}
_max=max(_max,now-size[x]);
if((_max<<1)<=now)G=x;
}
int K;
int dp;
int ans=0;
int R[maxn];
int RED;
void f_d(int x,int f,int dis)
{
ans=max(ans,dp+dis);
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v]&&v!=f)
{
f_d(v,x,dis+(R[v]>=RED));
}
}
}
void f_f(int x,int f,int dis)
{
dp=max(dp,dis);
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v]&&v!=f)
{
f_f(v,x,dis+(R[v]>=RED));
}
}
}
bool check1(int x,int k)
{
RED=k;
ans=dp=0;
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v])
{
f_d(v,x,(R[v]>=RED)+(R[x]>=RED));
f_f(v,x,R[v]>=RED);
}
if(ans>=K)return 1;
}
return 0;
}
int _max;
int ans0=0;
void solve1(int x,int now)
{
G=0;
f_g(x,0,now);
mark[x=G]=1;
int l=0,r=_max;
while(l<r)
{
int mid=((l+r+1)>>1);
if(check1(x,mid))l=mid;
else r=mid-1;
}
ans0=max(ans0,l);
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v])
{
solve1(v,size[v]<size[x]?size[v]:now-size[x]);
}
}
}
bool check2(int x,int k)
{
RED=k;
ans=dp=0;
for(int i=head[x];i;i=g[i].next)
{
int v=g[i].to;
if(!mark[v])
{
f_d(v,x,(R[v]>=RED)+(R[x]>=RED));
f_f(v,x,R[v]>=RED);
}
if(ans>=K-1)return 1;
}
return 0;
}
int solve2(int x,int now){
for(int i=1;i<=now;i++)mark[i]=0;
mark[x]=0;
int l=0,r=_max;
while(l<r)
{
int mid=((l+r+1)>>1);
if(check2(x,mid))l=mid;
else r=mid-1;
}
return l;
}
int n,M,Q;
int rq[maxn],q[maxn];
int ans2[maxn];
int main()
{
n=in();K=in();M=in();Q=in();
int i,j;
for(i=1;i<=n;i++){
R[i]=in();
// rq[R[i]]=i;
_max=max(_max,R[i]);
}
for(i=1;i<=M;i++)q[i]=in(),rq[q[i]]=i;
for(i=1;i<n;i++)
{
int a,b;
a=in();b=in();
add(a,b);
add(b,a);
}
solve1(1,n);
printf("%d\n",ans0);
for(i=1;i<=M;i++)
{
ans2[i]=solve2(q[i],n);
}
for(i=1;i<=Q;i++)
{
int h,s;
h=in();s=in();
if(K==1){
printf("%d\n",max(ans0,R[h]+s));
continue;
}
if(R[h]>ans0)printf("%d\n",ans0);
else{
int tmp=R[h]+s;
printf("%d\n",max(ans0,min(ans2[rq[h]],tmp)));
}
}
}
就算有了\(fread\)这代码也跑的飞飞飞慢……
应该还有比点分更快的做法,但是本人太弱,想不出来,看官老爷们见谅。




浙公网安备 33010602011771号