经典套路之增量贪心

增量贪心,一般就是基于一个原有的数值,然后在求下一个符合条件的数值时按照增量的大小进行贪心。这个还是一个很经典的套路的。不过我没记住,所以记录一下。
废话不多说了,直接看题。

纯情活泼的拆分

这道题其实是一道增量贪心模板题,但是可惜的是作者考场上为了展示找规律能力,结果到考试结束也没找到规律((
首先很显然,答案是 \(\mathcal O(\sqrt{V})\) 的,这个应该很显然吧;其次这 \(\sqrt{V}\) 的答案一定对应着连续的一段区间 \(n\)。那么我们只需知道这些答案区间的左端点或者右端点即可。怎么找呢?尝试找规律吧!继承我的意志
我们考虑增量贪心。我们假设我们知道答案 \(x\) 对应的区间左端点是 \(l\),那么我们如何找到 \(x+1\) 的区间左端点呢?
我们假设我们现在 \(l\)\(p_1,p_2,\cdots,p_k\),对应着 \(c_1,c_2,\cdots,c_k\),那么我们从 \(l\) 开始找显然是不差的。
我们枚举 \(p_1,p_2,\cdots,p_k\) 的幂次加一,即枚举 \(c_1+1,c_2+1,\cdots,c_k+1\),然后找出增量 \(p_1^{c_1+1}-p_2^{c_2},p_2^{c_2+1}-p_2^{c_2},\cdots\,p_k^{c_k+1}-p_k^{c_k}\) 的最小值 \(mn\),由于我们的 \(l\) 是答案 \(x\) 的最小值,那么我们知道 \(l+mn\) 必然是 \(x+1\) 答案区间的最小值。又因为答案必定是连续区间,所以更加显然。
然后就做完了,用优先队列维护增量就行了。要注意一点,如果现在增量等于 \(p\) 的最大值 \(p_k\) 了,那么我们就要将 \(p_k+1\) 入队。因为 \(p_k+1\) 以后将可能影响增量。
维护出来每一个答案区间最小值后,查询时就去二分答案就行了。
复杂度分析:答案最多为 \(\sqrt{V}\),优先队列有一个 \(\log\),所以复杂度是 \(\mathcal O(\sqrt{V}\log n+n\log n)\)

#include<bits/stdc++.h>
#define int long long
#define co const
using namespace std;
const int N=2e6+5;
struct Node
{
	int add,x,p;
	friend bool operator<(co Node &x,co Node &y)
	{
		return x.add>y.add;
	}
};
int a[N],cnt=2,id,n,ans[N],mn=1e18,now;
priority_queue<Node> q;
int ksm(int x,int y)
{
	int res=1;
	for(;y;y>>=1,x=x*x)
		if(y&1) res*=x;
	return res;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0); 
	q.push({2,2,1});
	while(now<=1e12)
	{
		Node tmp=q.top();q.pop();
		now+=tmp.add,a[++id]=now;
		q.push({ksm(tmp.x,tmp.p+1)-ksm(tmp.x,tmp.p),tmp.x,tmp.p+1});
		if(tmp.add>=cnt) cnt++,q.push({cnt,cnt,1}); 
	}
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x,pos;cin>>x;
		pos=upper_bound(a+1,a+1+id,x)-a-1;
		cout<<pos<<"\n";
	}
	return 0;
}
posted @ 2025-06-22 12:13  I_AK_CTSC  阅读(28)  评论(0)    收藏  举报