洛谷 P10181 龙逐千灯幻

题目背景

龙年到,帆帆也举起了自己的彩龙灯,他自己也要变成大彩龙啦!

题目描述

帆帆一共有 �n 盏龙灯,第 �i 盏的颜色是 ��ai​。

帆帆认为一段区间 [�,�][l,r] 的美观度 �(�,�)f(l,r) 为 ��⋯��al​⋯ar​ 中的不同颜色个数。

帆帆准备带着自己的龙灯去玩,他一共计划去玩 �m 天,第 �i 天,他会带着自己的 1⋯��1⋯xi​ 号龙灯,但是他发现如果把很多龙灯装在一起,那么别人只会注意到其中有多少种不同的颜色。

因此帆帆准备把这 ��xi​ 个龙灯按照编号顺序分成恰好 ��ki​ 个区间,满足每盏灯恰好在一个区间内。

那么帆帆这次出行的美观度就为所有区间的美观度的和。

请你帮帮帆帆最大化每一次出行的美观度。

输入格式

第一行五个整数 �,�,��,����,����n,m,id,seed,limx,分别代表序列长度,询问次数,以及 subtasksubtask 编号(样例中的 ��=0id=0),随机数种子,以及一个和询问有关的参数 ����limx。

因为本题输入量过大,因此采用如下方式生成询问:

我们使用下面的代码生成一个伪随机数列:

uint64_t PRG_state;
uint64_t get_number()
{
    PRG_state ^= PRG_state << 13;
    PRG_state ^= PRG_state >> 7;
    PRG_state ^= PRG_state << 17;
    return PRG_state;
}
int readW(int l,int r)
{
	return get_number()%(r-l+1)+l;
}

一开始 PRG_state=seed,你每次调用 readW(l,r) 会返回一个 [�,�][l,r] 内的随机数。

第二行 �n 个整数,第 �i 个整数代表 ��ai​。

设 ��,��,��xi​,ki​,ci​ 表示第 �i 组询问需要的参数 �,�,�x,k,c,其中 �c 的作用见输出格式,那么所有询问的参数可以用以下程序生成:

for (int i = 1; i <= m; ++i) {
	x[i] = readW(limx, n);
	k[i] = readW(1, x[i]);
	c[i] = readW(0, 1e7);
}

注:请不要在运行上述代码段获得各组询问的参数之前调用 readW() 函数,否则无法获得正确的询问信息。 本题不需要利用该伪随机数生成器的特殊性质,你只需将 get_number() 视为一个每次调用独立且均匀随机地生成一个无符号 6464 位整数的生成器即可。本题也不需要优化伪随机数生成器的内部实现。

输出格式

为了减少输出量,我们使用如下方式进行信息压缩:

如果第 �i 组询问的答案为 ����ansi​,其生成参数为 ��ci​,那么你需要输出:

⨁�=1�(����×��)i=1⨁m​(ansi​×ci​)

其中 ⨁⨁ 为异或运算,该值显然一定位于 long long 能表示的整数范围内。

输入输出样例

输入 #1复制

5 5 0 956144375 1
2 4 1 5 2 

输出 #1复制

21971409

输入 #2复制

10 10 0 478178732 1
2 2 1 1 2 1 2 1 2 1 

输出 #2复制

2834792

说明/提示

【样例1解释】

询问分别是:

3 1 6121576
5 3 3089509
1 1 4506170
3 1 2821007
1 1 7941511

答案分别是:

3
5
1
3
1

对于第一组询问,要分成一个区间,那么就是 [1,3][1,3],美观度就是 �(1,3)=3f(1,3)=3 。

对于第二组询问,最优的方案是分成 [1,3],[4,4],[5,5][1,3],[4,4],[5,5],美观度是 �(1,3)+�(4,4)+�(5,5)=5f(1,3)+f(4,4)+f(5,5)=5

后三个询问同理。

【样例2解释】

询问分别是:

8 4 6858024
3 2 236530
2 2 8140891
5 3 4562139
8 7 4749403
7 4 4319971
5 1 5063575
3 1 7343109
6 2 1566851
3 1 7959241

询问答案分别是:

7
3
2
5
8
7
2
2
4
2

【数据范围】

本题采用捆绑测试。

  • 子任务一(1010 分):1≤�≤5001≤n≤500。
  • 子任务二(1515 分):1≤�≤30001≤n≤3000。
  • 子任务三(1515 分):�=1m=1。
  • 子任务四(2020 分):1≤��≤301≤ai​≤30。
  • 子任务五(2020 分):1≤�≤4×1041≤n≤4×104。
  • 子任务六(2020 分):无特殊限制。

对于 100%100% 的数据,1≤�≤1051≤n≤105,1≤�≤1061≤m≤106,0≤����≤1090≤seed≤109,1≤����≤�1≤limx≤n,1≤��≤�1≤ai​≤n。

subtask 1

设 ���,�dpi,j​ 表示把前 �i 个位置划分成 �j 段的最小代价,转移:

���,�=max⁡�=1����−1,�−1+�(�,�)dpi,j​=k=1maxi​dpk−1,j−1​+f(k,i)

其中 �(�,�)f(k,i) 为区间 [�,�][k,i] 内的颜色数,可以预处理出来。

询问是 �(1)O(1) 的,总复杂度 �(�3+�)O(n3+m)。

subtask 2subtask 2

有至少两个做法。

sol 1

优化第一部分的 DP,以 �j 为阶段,那么我们只需要维护 ���−1,�−1+�(�,�)dpk−1,j−1​+f(k,i) 的最大值。

开一棵线段树,第 �k 个位置维护 ��=���−1,�−1+�(�,�)gk​=dpk−1,j−1​+f(k,i),当我们 �→�+1i→i+1 时:

  • ��+1=���−1,�gk+1​=dpj−1,k​
  • ∀�>����+1,��=��+1∀k>lsti+1​,gk​=gk​+1

其中 ����lsti​ 为 �i 左边第一个等于 ��ai​ 的数的下标,没有则为 00。

上面的转移只需要线段树支持区间加即可。

询问还是 �(1)O(1) 的,总复杂度 �(�2log⁡�+�)O(n2logn+m)。

sol 2

我们不难证明 �(�,�)f(k,i) 满足四边形不等式,因此 DP 可以用决策单调性优化。

直接分治,两个指针移动来维护 �(�,�)f(k,i),总复杂度 �(�2log⁡�+�)O(n2logn+m)。

subtask 3subtask 3

因为 �=1m=1,所以我们只需要解决一个询问。

由于蒙日矩阵 max⁡+max+ 卷积的 �k 次幂的每个位置都关于 �k 凸,因此 ���,�dpi,j​ 关于 �j 是一个凸函数,这种区间划分问题的经典做法是 ���wqs 二分。

二分斜率 �c,转移为:

���=max⁡����−1+�(�,�)−�dpi​=kmax​dpk−1​+f(k,i)−c

然后同上用线段树优化转移,复杂度 �(�log⁡2�+�)O(nlog2n+m)。

subtask 4subtask 4

我们考虑一下 ���wqs 二分的斜率 �c。

那么因为 ��≤30ai​≤30,所以 �(�,�)≤30f(k,i)≤30,那么当 �>30c>30 时,�(�,�)−�<0f(k,i)−c<0,此时切点一定是分一段,因此只有 �≤30c≤30 的 �c 有意义。

对于 �=0⋯30c=0⋯30 各做一遍线段树优化 DP,存下每个前缀的答案即可。

复杂度 �(max⁡���log⁡�+�)O(maxai​nlogn+m)。

subtask 5subtask 5

上一个做法告诉我们,当 �c 过大时,切点就会很小。

我们可以具体分析一下,设 �(�)=��∗,�,�(�)=�(�)−�(�−1)F(k)=dp∗,k​,D(k)=F(k)−F(k−1),因为 �F 是凸函数,所以 �(�)≥�(�+1)D(k)≥D(k+1) 恒成立。

同时,因为 �(�)≤�F(k)≤n,那么有 (�−1)�(�)≤∑�=2��(�)≤�(�)−�(1)≤�(k−1)D(k)≤∑i=2k​D(i)≤F(k)−F(1)≤n,即 �(�)≤⌊��−1⌋D(k)≤⌊k−1n​⌋。

设斜率为 �c 时的切点为 �(�)G(c) ,那么 �(�(�)−1)−�×(�(�)−1)≤�(�(�))−�×�(�)F(G(c)−1)−c×(G(c)−1)≤F(G(c))−c×G(c) ,即 �(�(�))−�(�(�)−1)≥�F(G(c))−F(G(c)−1)≥c ,�≤�(�(�))≤⌊��(�)−1⌋c≤D(G(c))≤⌊G(c)−1n​⌋。

我们取阈值 �B,

  • 对于 �≤�k≤B 的询问,我们可以预处理所有普通 DP 值。

  • 对于 �>�k>B 的询问,根据上面的性质,�(�)≥�G(c)≥k 的 �c 满足 �≤⌊��⌋c≤⌊Bn​⌋ ,我们预处理这些 �c 对应的 DP 值。

都采用线段树优化,单次 DP 都是 �(�log⁡�)O(nlogn),取 �=�B=n​,我们预处理的复杂度是 �(��log⁡�)O(nn​logn)。

询问时,前部分可以 �(1)O(1) 回答,后半部分可以直接二分,不过这样空间是 �(��)O(nn​) 的。

同时注意到 �(�)G(c) 是单调的,因此可以把询问按照 �k 排序后,用一个指针维护转移点,空间就是线性了。

排序可以用桶排序,该做法时间复杂度 �(��log⁡�+�)O(nn​logn+m),空间复杂度 �(�+�)O(n+m)。

注意到线段树的常数比较大,如果被卡常可以把第一部分的 ��dp 换成决策单调性分治,因为访问时连续的所以常数小很多。如果实现较好,也可以直接获得满分。

subtask 6subtask 6

考虑能不能把线段树的 log⁡log 去掉。

观察一下我们实际需要支持的操作:

  • 向末尾加入一个数
  • 后缀加 11
  • 求最大值

维护一个单调递减的单调栈,那么 11 操作可以直接不断弹栈维护,33 操作就是查询栈底元素。

对于 22 操作,我们找到该后缀在单调栈上对应的位置,这个可以用并查集维护,然后相当于把这个部分往前面合并弹出若干元素,最后打上 +1+1 标记。

因为要支持中间弹出元素,所以我们用链表维护这个单调栈,至于 +1+1 标记,我们可以发现我们相当于只需要查询栈底,查询栈顶,查询某相邻两个位置的差值,因此直接维护单调栈内元素的差分值即可,打标记是简单的。

瓶颈在于并查集,因此单次 ��dp 的复杂度优化到了 �(��(�))O(nα(n)),如果采用严格线性并查集,我们可以做到 �(�)O(n)。

剩下部分不变,复杂度 �(��+�)O(nn​+m),空间 �(�+�)O(n+m),可以通过此题。

值得一提的是,在本题中你可以发现,我们优化单次 DP 和优化多次询问的部分是独立的,也就是说,我们把 �(�,�)f(l,r) 换成任意凸函数,在值域不大的情况下都可以在根号的代价内求出所有函数值。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
uint64_t PRG_state;
uint64_t get_number()
{
    PRG_state ^= PRG_state << 13;
    PRG_state ^= PRG_state >> 7;
    PRG_state ^= PRG_state << 17;
    return PRG_state;
}
int readW(int l,int r)
{
	return get_number()%(r-l+1)+l;
}
int n,m,tid,limx;
int a[N],lst[N],al[N];
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
PII f[N],g[N];
PII operator +(PII A,PII B){return mk(A.first+B.first,A.second+B.second);}
PII operator -(PII A,PII B){return mk(A.first-B.first,A.second-B.second);}
vector<PII> qry[N];
int nxt[N],pre[N],jump[N];
PII tag[N],Wl,Wr;
int get(int x)
{
	if(x==jump[x])return x;
	return jump[x]=get(jump[x]); 
}
inline int del(int x)
{
	int p=pre[x];
	nxt[pre[x]]=nxt[x];
	pre[nxt[x]]=pre[x];
	pre[x]=nxt[x]=0;
	return p;
}
inline void push(int x,PII v)
{
	tag[x]=mk(0,0);
	jump[x]=x;
	pre[x]=x-1;
	nxt[x-1]=x;
	nxt[x]=0;
	int l=x-1;
	while(l)
	{
		if(Wr>v)break;
		Wr=Wr-tag[l];
		jump[l]=x;
		l=del(l);
	}
	if(!l) Wl=Wr=v;
	else 
	{
		nxt[l]=x;
		pre[x]=l;
		tag[x]=v-Wr;
		Wr=v;
	}
}
void update(int x)
{
	x=get(x);
	int l=pre[x];
	while(l)
	{
		if(!(tag[x].first==0||(tag[x].first==-1&&tag[x].second>0)))break;
		jump[l]=x;
		tag[x]=tag[x]+tag[l];
		l=del(l);
	}
	if(!l) 
	{
		Wl=Wl+tag[x];
		tag[x]=mk(0,0);
		Wl.first++;
		Wr.first++;
	}
	else 
	{
		nxt[l]=x;
		pre[x]=l;
		tag[x].first++;
		Wr.first++;
	}
}
const int inf = 1e7;
int now[N],siz[N],len[N];
int main()
{
	cin>>n>>m>>tid>>PRG_state>>limx;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		al[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int x=readW(limx,n);
		int k=readW(1,x);
		int c=readW(0,1e7);
		qry[x].push_back(mk(k,c));
	}
	for(int i=1;i<=n;i++)sort(qry[i].begin(),qry[i].end()),len[i]=(int)qry[i].size();
	LL ans=0;
	for(int i=0;i<=n;i++)f[i]=g[i]=mk(-inf,-inf);
	g[0]=mk(0,0);
	int up=0;
	for(int k=1;;k++)
	{
		for(int i=1;i<=n;i++)
		{
			push(i,g[i-1]);
			update(al[i]+1);
			f[i]=Wl;
		}
		up=f[n].first-g[n].first;
		for(int i=0;i<=n;i++)
		{
			g[i]=f[i];
			f[i]=mk(-inf,0);
		}
		for(int i=k;i<=n;i++)
		{
			while(now[i]<len[i]&&qry[i][now[i]].first==k)
		 	{
				ans^=(1ll*qry[i][now[i]].second*g[i].first);
				now[i]++;
			}
		}
		if(k>1&&up*1<=k)break;
	}
	for(int i=1;i<=n;i++)siz[i]=qry[i].size()-1;
	for(int w=0;w<=up;w++)
	{
		f[0]=mk(0,0);
		int flag=0;
		for(int i=1;i<=n;i++)
		{
			push(i,f[i-1]);
			update(al[i]+1);
			f[i]=Wl+mk(-w,-1);
            while(siz[i]>=now[i]&&qry[i][siz[i]].first>=-f[i].second) 
			{
				ans^=(1ll*qry[i][siz[i]].second*(f[i].first+w*qry[i][siz[i]].first));
				siz[i]--;
			}
			if(siz[i]<now[i])flag++;
		}
		if(flag==n)break;
	}
	cout<<ans;
	return 0;
}

拜拜! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值