【ICPC】2022银川站 G. Photograph | 模拟、链表

424 阅读3分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【ICPC】2022银川站 G. Photograph | 模拟、链表

题目链接

Problem - G - Codeforces

题目

image.png

题目大意

nn 个学生拍照片,他们的学号分别是 1,2,...,n1,2,...,n。他们要在 q+1q+1 个地点拍照。第〇次拍照他们到达的顺序是 p1,p2,...,pnp_1,p_2,...,p_n。换言之,首先学生 p1p_1 到达了拍摄地点,接着是学生 p2p_2……每当一个学生到达拍照地点都会拍摄一张照片,即在每个拍摄地点会拍摄 nn 张照片,每张照片中出现的学生都会按学号的大小顺序排列。

每张照片的分数是照片上出现的所有的人相邻两个人的身高差的平方之和。第 ii 个地点的分数是该地点拍摄的 nn 张照片的分数之和,记为 ansians_i

在前往第 i+1i+1 个地点时,学生的到达顺序将会变为到达第 ii 个地点的顺序向左轮换 ansi+kians_i+k_i 次之后的结果。

输出 q+1q+1 每个地点的分数。

思路

发现 qq 很小,我们可以使用比较暴力的解法。

对于第 ii 个地点,我们首先求出本次学生到达的顺序。考虑怎样求出某学生 jj 到达拍照场地时,他左右两边的人是谁。发现最初场地非常空旷,我们不太容易找到某人的插入位置,于是倒着做,我们把问题视为场地中最初有 nn 个人,他们按 pn,pn1,...,p1p_n,p_{n-1},...,p_1 的顺序离开场地,离开前拍摄照片。则我们可以维护 ljl_jrjr_j 表示当前 jj 左边和右边的人,当 jj 离开场地,我们只需要 O(1)O(1) 执行双向队列出队操作,即修改 rljr_{l_j}lrjl_{r_j}

在求出某学生 jj 到达拍照场地时,他左右两边的人是谁后,本张照片的分数就是上一张照片的分数减去 jj 左右两边的人的贡献再加上 jj 和他左边的人的贡献和 jj 和他右边的人的贡献。左右有一边(甚至两边)没有人的情况,也可以相似计算。

最终时间复杂度 O(nq)O(nq),本题难度较低,感觉通过人数这么少主要是榜歪掉了。

代码

#include <bits/stdc++.h>

using namespace std;
using LL=long long;
const int N=100001;
int pre[N],nxt[N];
LL hgt[N];
int p[N],np[N];
int n,q,d[N*2],h,t,l[N],r[N];
void shift(LL k)
{
	for (int i=1;i<=n;++i)
	{
		l[i]=i-1;
		r[i]=i+1;
		if (i-k<=0) np[i-k+n]=p[i];
		else np[i-k]=p[i];
	}
	r[n]=0;
	for (int i=n;i>=1;--i)
	{
		p[i]=np[i];
		pre[p[i]]=l[p[i]];
		nxt[p[i]]=r[p[i]];
		r[l[p[i]]]=r[p[i]];
		l[r[p[i]]]=l[p[i]];
	}
}
	
LL getans()
{	
	LL ans=0,sum=0;
	for (int i=2;i<=n;++i)
	{
		int t=p[i];
		if (pre[t]&&nxt[t])
			sum-=(hgt[pre[t]]-hgt[nxt[t]])*(hgt[pre[t]]-hgt[nxt[t]]);
		if (pre[t]) 
			sum+=(hgt[t]-hgt[pre[t]])*(hgt[t]-hgt[pre[t]]);
		if (nxt[t])  
			sum+=(hgt[t]-hgt[nxt[t]])*(hgt[t]-hgt[nxt[t]]);
		ans+=sum;
	}
	return ans;
}
int main() 
{
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;++i) scanf("%lld",&hgt[i]);
	for (int i=1;i<=n;++i) scanf("%d",&p[i]);
	shift(0);
	LL ans=getans();
	printf("%lld\n",ans);
	for (int i=1,k;i<=q;++i)
	{
		scanf("%d",&k);
		shift((ans+k)%n);
		ans=getans();
		printf("%lld\n",ans);
	}
	return 0;
}