CF2035D

CF2035D

这道题是 gpt 出的,但是我做不出来,是时候被替代了。

分析

把每个数不断除以二直到得到最小的奇数,记录一下我们能在这个数上提出来的 \(2\) 的个数。

首先想到的是贪心地把 \(2\) 全部分配给最大的,但是贪心甚至过不了样例。

但是想一想如果 \(i<j\) 的限制不存在,我们直接把所有的 \(2\) 都分配给最大的处理过后的数 ,那肯定是正确的。

能不能推广到这道题上面来?
当然可以,我们首先还是预处理把所有的数里的 \(2\) 都给提出来。

特殊情况

假设有一个子序列,最后一个数就是这个序列里面的最大值,那么是不是就变成了我们提到的 "\(i<j\) 的限制不存在"的情况了?
因为显然我们会把所有的 \(2\) 都给最后一个数,使得答案最大。

初步思考

仔细想想,这其实是一个单调栈的过程,栈里装的是我们处理过后的数字且递减,对于其中的任意一个元素 \(stk_x\) ,假设他的上一个元素师 \(stk_{x-1}\) ,那么我们一定会把 \((stk_{x-1},stk_x]\) 之间的所有 \(2\) 都乘给 \(stk_x\) ,因为显然这是这一部分 \(2\) 的最好分配方式了。

进一步思考

你会发现这样应该通过不了样例。

如果说进行完上述操作之后得到的 \(stk_x'\) 就大于 \(stk_{x-1}\) 了呢?那么我们显然是会把 \(stk_{x-1}\) 之前的所有 \(2\) 也都乘给 \(stk_x\) 的呀!

实际上这是一个把问题不断划分成子问题的过程。

正序枚举

由于有 \(i<j\) 这个限制,那么我们发现,如果正序枚举,当前区间的分配方式对于全局来说,不一定就是最优的,如果后面出现了更大的数,那么当前区间的 \(2\) 可以转而分配给后面的区间。

倒序枚举

如果我们倒叙枚举呢?

这时候就可以十分顺理成章地更新出最优答案,而且我们已经算出的答案是不会受前面的影响的。

写法

可是我们显然是不能够对于每一个前缀都去倒叙枚举,这样复杂度就变成 \(O(n^2)\) 了。

这时候就应该考虑通过继承的方式来快速计算每一个答案,当前加入一个数,我们不断地通过单调栈的模拟把他和前面的合并,直到满足 \(stk_x'<stk_{x-1}\) 结束。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,MOD=1e9+7;
template<typename T>inline void re(T &x)
{
	x=0;int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x<0)putchar('-'),x=-x;
	if(x>9)wr(x/10);
	putchar(x%10^48);
}
inline void debug(int x){wr(x),putchar('\n');}
int n,T;
int a[N];
int s[N],sc[N];
int calc(int &x)
{
	int ans=0;
	while(x%2==0&&x!=0)
	{
		ans++;
		x>>=1;
	}
	return ans;
}
inline int power(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans%MOD;
}
int p(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
int ans[N];
int stk[N];
inline void solve()
{
	re(T);
	while(T--)
	{
		re(n);
		int top=0;
		for(register int i=1;i<=n;++i)
		{
			re(a[i]);
			sc[i]=calc(a[i])+sc[i-1];
			s[i]=(s[i-1]+a[i])%MOD;
			while(top&&a[i]*p(2,sc[i]-sc[stk[top]])>=a[stk[top]])
				top--;
			ans[i]=((ans[stk[top]]+a[i]*power(2,sc[i]-sc[stk[top]])%MOD)%MOD+(s[i-1]-s[stk[top]])+MOD)%MOD;
			stk[++top]=i;
			wr(ans[i]),putchar(' ');
		}
		putchar('\n');
	}
}
signed main()
{
	solve();
	return 0;
}

小细节

注意在比较 \(stk_x'\)\(stk_{x-1}\) 的时候不能取模,不然有失正确性,但是由于数据不太强,仍然可以通过前 \(24\) 个点。

\(25\) 这个点是一个非常好的hack,如下:

Input:
1
3
999999998 2097152 1048576
Expected output:
999999998 2097143 546480318 
posted @ 2024-10-28 20:10  Hanggoash  阅读(29)  评论(0编辑  收藏  举报
动态线条
动态线条end