ACwing 334 K匿名序列

首先这道题很容易发现如果已经知道了最后的答案序列,那么操作顺序是无所谓的

所以我们可以假设从头操作到尾

由于题目给的是非严格递增序列,我们猜想最后的答案一定是一段一段的,段与段之间单调递增

比如1 1 1 2 2 2 2 2 3 3 4 5 5

反证:如果最终的答案序列存在aiaj,其中ijaiaj,那我们让ai变成ajaj变成ai,则改变后的序列仍然符合题意而且总操作次数要么不变要么变小,所以最终的答案序列没有逆序对

这样就简单了,我们设fi表示前i个数字的最小操作次数,考虑最后一个数字最终会变成什么样,由题,每一段的大小是大于等于k的

所以有fi=minj=ki(fij+k=ij+1i(akaij+1))

这里运用了一个小贪心:每一段的最开始的数字一定不会变化,变化了肯定没有不变化优

拆开后发现ij不好看,为了符合习惯,让m=ij

于是有smfmmam+1=iam+1fi+si

这里发现斜率是负数,而且绝对值越来越大,不能运用让队头出队,必须要运用二分查找

然而,这个时候我们只需要将原式取反就迎刃而解了:

sm+fm+mam+1=iam+1+fisi

然后就是套模板了

代码有注释,一定要看

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+10;
int t;
int n,k;
ll a[N],s[N],f[N];
int l,r,q[N];
ll F(int x)
{
	return f[x]+x*a[x+1]-s[x];
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&k);
		for(int i=1;i<k;i++) f[i]=0x7fffffff;
		//这个初始化一定要有
		//因为当i<k时是不合法的
		//我们不能让之后的状态从这些状态转移过来 
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			s[i]=s[i-1]+a[i];
		}
		l=r=1;
		q[1]=0;
		for(int i=k;i<=n;i++)
		{
			while(l<r&&F(q[l+1])-F(q[l])<=i*(a[q[l+1]+1]-a[q[l]+1])) l++;
			f[i]=F(q[l])+s[i]-i*a[q[l]+1];
			while(l<r&&(F(i-k+1)-F(q[r]))*(a[q[r]+1]-a[q[r-1]+1])<=(F(q[r])-F(q[r-1]))*(a[i-k+1+1]-a[q[r]+1])) r--;
			q[++r]=i-k+1;
			//这里放的是i-k+1
			//对应的是博客中的m 
		}
		printf("%lld\n",f[n]);
	 } 
	return 0;
}

然而第二遍做的时候还发现了一个新思路,从左往右考虑这个序列,如果某一段的个数符合题意,那么直接跳过,如果某一段的个数比题意少,那么要么是把这一段的所有数给减到前面一段相同的数字,要么是把紧接着的后面若干个数降到这个数,这里若干个数就是k这一段的个数,然后用记忆化搜索进行实现就可以了

但是不知道这个思路是否正确

update 2024.9.10

主要就是提醒一下自己,脑子别犯浑了,负数斜率直接取相反数就好了

posted @   最爱丁珰  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示