倍增:喷泉 深度解析(洛谷P7167)

洛谷传送门
在这里插入图片描述
在这里插入图片描述

解析

什么破题
数据范围来看很明显最多到nlogn
首先,对于样例进行一下分析:
在这里插入图片描述
我们可以把它转化为一棵树:
在这里插入图片描述
每一个根都有对应的权,给你一个结点和加权和,问能爬到哪里
既然是树,自然要找爸爸(滑稽)
可以使用一个priority_queue来找:

for(int i=1;i<=n;i++){
		scanf("%d%d",&p[i].d,&p[i].c);
		p[i].id=i;
		while(!q.empty()){
			node m=q.top();
			if(m.d<p[i].d){//比当前这个小的
			//都出来认爸爸
				q.pop();
				p[m.id].father=i;
			}
			else break;
		}
		q.push((node){p[i].d,p[i].c,0,0,i});
	}

当然,最后还在队列中的就会无爸可归

孤苦伶仃不可怕,水池是我们共同的家

while(!q.empty()){
		node m=q.top();
		q.pop();
		p[m.id].father=0;
	}

既然是树,我们想到用倍增算法
用dp[i][j]表示从第i个盘接满(是指水真正经过)2^j个盘后所需的总水量
那么递推式就是:
。。。推不下去了
要想让这题得到递推式,我们需要得到dp[i][j-1]后水接到哪一个盘子了
那么就出现了:

dp结构体

(我都不敢写)

struct node2{
	int num,id;
}dp[100500][30];
//dp[i][j]num表示从i往下走
//2^j个有效的盘至少存的总水量,id表示到达的盘 

那么递推式就是(再来一遍 ):

dp[i][j].num=dp[i][j-1].num +
dp[p[dp[i][j-1].id].father][j-1].num;//因为id
//表示j-1操作后接满的盘,所以得从它的爸爸开始
dp[i][j].id=dp[p[dp[i][j-1].id].father][j-1].id;

关于初始化,显然:

dp[i][0].num=p[i].c;//2^0个就是把自己接满了
dp[i][0].id=i;

然后就是写个倍增

		scanf("%d%d",&r,&tot);
		int s=0,res,place=r,dep=p[r].deep;
		res=qu[p[r].deep];
		int flag=1;
		while(res>=0){
			//printf("res:%d\n",res);
			if(dep>=mi[res]&&
			dp[place][res].num + s<tot){
				s+=dp[place][res].num;
				place=p[dp[place][res].id].father;
				dep-=mi[res];
			}
			if(dp[place][res].num + s == tot){
				printf("%d\n",dp[place][res].id);
				flag=0;
				break;
			}
			res --;
		}
		if(flag) printf("%d\n",place);

最后一块拼图也已补齐,让我们

召唤神龙!!

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<string>
#include<queue>
#include<vector>
using namespace std;
int n,x;
long long sum[100500]={ };
struct node{
	int d,c,father,deep,id;
	bool operator < (const node& oth) const{
		return d > oth.d;
	}
}p[100500];
struct node2{
	int num,id;
}dp[100500][30];
//dp[i][j]num表示从i往下走2^j个有效的盘至少存的总水量,id表示到达的盘 
priority_queue<node>q;
int mi[20];
int qu[100500];
void solve(){
	mi[0]=1;
	for(int i=1;i<=18;i++){
		mi[i]=mi[i-1] * 2;
	}
	int k=1;
	for(int i=1;i<=n;i++){
		if(mi[k]<=i) k++;
		qu[i]=k-1;
	}
}
int main(){
	int flag=1;
	scanf("%d%d",&n,&x);
	solve();
	for(int i=1;i<=n;i++){
		scanf("%d%d",&p[i].d,&p[i].c);
		p[i].id=i;
		while(!q.empty()){
			node m=q.top();
			if(m.d<p[i].d){
				q.pop();
				p[m.id].father=i;
			}
			else break;
		}
		q.push((node){p[i].d,p[i].c,0,0,i});
	}
	while(!q.empty()){
		node m=q.top();
		q.pop();
		p[m.id].father=0;
	}
	for(int i=n;i>=1;i--){
		if(p[i].father==0) p[i].deep=1;
		else p[i].deep=p[p[i].father].deep+1;
	}
	int k=qu[n];
	//printf("---------\n");
	for(int i=1;i<=n;i++){
		dp[i][0].num=p[i].c;
		dp[i][0].id=i;
		//printf("%d:%d\n",i,p[i].father);
	}
	//printf("---------\n");
	for(int j=1;j<=k;j++){
		for(int i=1;i<=n;i++){
			if(j>qu[p[i].deep]) continue;
			dp[i][j].num=dp[i][j-1].num +
			dp[p[dp[i][j-1].id].father][j-1].num;
			dp[i][j].id=dp[p[dp[i][j-1].id].father][j-1].id;
			//printf("%d %d: %d %d %d\n",i,j,dp[i][j].num,
			//dp[i][j-1].num,dp[dp[i][j-1].id][j-1].num); 
		}
	}
	int r,tot;
	/*printf("---------\n");
	for(int i=1;i<=n;i++){
		printf("%d : %d %d %d\n",i,p[i].deep,
		qu[p[i].deep],dp[i][qu[p[i].deep]].num);
		printf("\n");
	}
	printf("---------\n");*/
	for(int k=1;k<=x;k++){
		scanf("%d%d",&r,&tot);
		int s=0,res,place=r,dep=p[r].deep;
		res=qu[p[r].deep];
		int flag=1;
		while(res>=0){
			//printf("res:%d\n",res);
			if(dep>=mi[res]&&dp[place][res].num + s<tot){
				s+=dp[place][res].num;
				place=p[dp[place][res].id].father;
				dep-=mi[res];
				//printf("%d : %d %d %d\n",r,place,s,res);
			}
			if(dp[place][res].num + s == tot){
				printf("%d\n",dp[place][res].id);
				flag=0;
				break;
			}
			res --;
		}
		if(flag) printf("%d\n",place);
	}
	return 0;
}

(无处不在的printf可见我究竟调了多久)

AC快乐!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值