您的位置:首页 > 其它

AtCoder Regular Contest 077E: guruguru 题解

2017-07-02 20:39 477 查看
记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即可

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

差分数组相当于一个前缀和数组,维
4000
护的是后一项比前一项多多少,所以我们可以发现一个区间+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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: