AtCoder Regular Contest 077E: guruguru 题解

博客详细解析了AtCoder Regular Contest 077E题目,通过线段树实现区间内步数优化,并探讨了一次函数关系式的区间修改方法。此外,介绍了差分数组在区间修改上的高效性,阐述了如何利用差分数组进行区间操作以达到O(n)的时间复杂度。
摘要由CSDN通过智能技术生成

记favourite level=x

我们可以发现,假设a[i]<a[i+1],那么如果x取a[i]+1~a[i+1]之间的值,那么可以省下的步数满足关系式f(x)=x-a[i]-1

于是我们可以用线段树在这个区间上打一个公差为一的递增标记,最后对1~m的每个点做单点查询,看哪个能省下最多的步数

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <map>
#include <stack>
#include <set>
#include <vector>
#include <queue>
#include <deque>
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define LL long long
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const int INF=0x7ffffff;
const int magic=348;

struct node
{
	int left,right;
	LL tag;
	int cnt;
}tree[300048];

void build(int cur,int left,int right)
{
	tree[cur].left=left;tree[cur].right=right;
	tree[cur].tag=tree[cur].cnt=0;
	if (left!=right)
	{
		int mid=(left+right)>>1;
		build(cur*2,left,mid);
		build(cur*2+1,mid+1,right);
	}
}

void update(int cur,int left,int right,int st)
{
	if (left<=tree[cur].left && tree[cur].right<=right)
	{
		tree[cur].tag+=st+tree[cur].left-left;
		tree[cur].cnt++;
		return;
	}
	int mid=(tree[cur].left+tree[cur].right)>>1;
	if (left<=mid) update(cur*2,left,right,st);
	if (mid+1<=right) update(cur*2+1,left,right,st);
}

LL query(int cur,int pos)
{
	if (tree[cur].left==tree[cur].right)
	{
		return tree[cur].tag;
	}
	int mid=(tree[cur].left+tree[cur].right)>>1;LL res;
	if (pos<=mid) res=query(cur*2,pos); else res=query(cur*2+1,pos);
	res+=(pos-tree[cur].left)*tree[cur].cnt+tree[cur].tag;
	return res;
}

int n,m;
int a[100048];

int main ()
{
	int i;
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	build(1,1,m);
	for (i=1;i<=n-1;i++)
	{
		if (a[i]<a[i+1] && a[i+1]-a[i]>=2) update(1,a[i]+2,a[i+1],1);
		if (a[i]>a[i+1])
		{
			if (a[i]+2<=m) update(1,a[i]+2,m,1);
			if (m-a[i]>=1)
				update(1,1,a[i+1],m-a[i]);
			else
				if (a[i+1]>=2) update(1,2,a[i+1],1);
		
		}
	}
	LL ans=0,res,ans_res=-1;
	for (i=1;i<=n-1;i++)
		if (a[i]<a[i+1]) ans+=a[i+1]-a[i]; else ans+=m-(a[i]-a[i+1]);
	for (i=1;i<=m;i++)
	{
		res=query(1,i);
		if (res>ans_res) ans_res=res;
	}
	cout<<ans-ans_res<<endl;
	return 0;
}

但是这个做法还是慢了一些,是O(nlogn)的,我们不妨思考有没有更快的做法

事实上,满足一次函数关系式的区间修改有一种更好的修改方法:单独维护k和b,这样在这个区间中我们只要在斜率(k)标记上+1,在b标记上减去a[i]+1即可

对一个区间统一加上一个值有更快的做法,即差分数组

差分数组相当于一个前缀和数组,维护的是后一项比前一项多多少,所以我们可以发现一个区间+1,区间内的相邻的数的差是不变的,所以只要在区间头+1,区间尾-1即可

于是这样可以做到O(n)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <map>
#include <stack>
#include <set>
#include <vector>
#include <queue>
#include <deque>
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define LL long long
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const int INF=0x7ffffff;
const int magic=348;

int n,m;
int a[200048];
LL s1[200048],s2[200048];

int main ()
{
	int i;
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++) scanf("%d",&a[i]);
	int from,to;
	for (i=1;i<=n-1;i++)
	{
		from=a[i];to=a[i+1];
		if (from>to) to+=m;
		s1[from+1]+=from+1;s1[to+1]-=(from+1);
		s2[from+1]++;s2[to+1]--;
	}
	for (i=2;i<=m*2;i++)
	{
		s1[i]+=s1[i-1];
		s2[i]+=s2[i-1];
	}
	LL ans=0,res,fans=1e16;
	for (i=1;i<=n-1;i++) if (a[i]<a[i+1]) ans+=a[i+1]-a[i]; else ans+=m-(a[i]-a[i+1]);
	for (i=1;i<=m;i++)
	{
		res=s2[i]*i+s2[i+m]*(i+m)-s1[i]-s1[i+m];
		if (ans-res<fans) fans=ans-res;
	}
	cout<<fans<<endl;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值