CodeForces - 1527E Partition Game(dp+线段树)

Posted Frozen_Guardian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CodeForces - 1527E Partition Game(dp+线段树)相关的知识,希望对你有一定的参考价值。

题目链接:点击查看

题目大意:给出一个长度为 n n n 的数列,现在需要将其划分成 k k k 段,使得贡献和最小

对于每段区间 [ l , r ] [l,r] [l,r] 的贡献为,其中每个数字,其最后一次出现的位置减去其首次出现的位置

输出最小贡献

题目分析:区间划分,经典的 d p dp dp 问题, d p i , j dp_{i,j} dpi,j 代表前 i i i 个数划分为 j j j 个区间的最优解,两种转移方程:

  1. d p i , j = m i n ( d p i − 1 , j + ( a i 属 于 上 一 段 的 贡 献 ) , d p i − 1 , j − 1 + ( a i 独 成 一 段 的 贡 献 ) ) dp_{i,j}=min(dp_{i-1,j}+(a_i属于上一段的贡献),dp_{i-1,j-1}+(a_i独成一段的贡献)) dpi,j=min(dpi1,j+(ai),dpi1,j1+(ai))
  2. d p i , j = m i n ( d p k , j − 1 + v a l ( k + 1 , i ) ) dp_{i,j}=min(dp_{k,j-1}+val(k+1,i)) dpi,j=min(dpk,j1+val(k+1,i))

以上,第一种转移方程的时间复杂度是 O ( n ∗ m ) O(n*m) O(nm) 的,缺点是无法知晓最后一段区间的起点

第二种转移方程的时间复杂度是 O ( n ∗ n ∗ m ) O(n*n*m) O(nnm) 的,可以知晓最后一段区间的起点

就本题而言,我们只能选择第二种转移方程,所以考虑利用数据结构优化掉一维 n n n

不难发现对于动态规划的第二维是可以滚动的,所以不妨在固定第二维后,将第一维的信息扔进线段树里,这样就可以 O ( l o g n ) O(logn) O(logn) 去维护区间最小值了,此时线段树中的每个节点实质上维护的是 d p i + v a l ( i + 1 , c u r ) dp_i+val(i+1,cur) dpi+val(i+1,cur),其中 c u r cur cur 就是第一维更新到的位置, v a l ( l , r ) val(l,r) val(l,r) 就是区间 [ l , r ] [l,r] [l,r] 中的贡献,也就是题目所述

查询的话直接查询区间最小值即可,现在问题转换为了,在加入 a i a_i ai 后,如何更新线段树中的信息呢?

感觉本题的难点就是这里了,我思考到这里卡住了,去请教的冰哥,明白了之后就豁然开朗了

因为从前往后加入 a i a_i ai 之后,并不会影响 a i a_i ai 首次出现的位置,只会影响 a i a_i ai 最后一次出现的位置,所以我们记 l a s t [ a i ] last[a_i] last[ai] a i a_i ai 最后一次出现的位置,此时就将 [ 0 , c u r ] [0,cur] [0,cur] 的区间划分为了两段: [ 0 , l a s t [ a i ] − 1 ] , [ l a s t [ a i ] , c u r ] [0,last[a_i]-1],[last[a_i],cur] [0,last[ai]1],[last[ai],cur],为什么这样划分呢,注意到上面提到的,线段树中节点维护的信息是: d p i + v a l ( i + 1 , c u r ) dp_i+val(i+1,cur) dpi+val(i+1,cur),当此处的 i i i 取到 [ l a s t [ a i ] , c u r ] [last[a_i],cur] [last[ai],cur] 时, v a l ( i + 1 , c u r ) = 0 val(i+1,cur)=0 val(i+1,cur)=0,即不做贡献,而取到 [ 0 , l a s t [ a i ] − 1 ] [0,last[a_i]-1] [0,last[ai]1] 时, v a l ( i + 1 , c u r ) val(i+1,cur) val(i+1,cur) 统一增大了 i − l a s t [ a i ] i-last[a_i] ilast[ai],所以只需要用线段树区间更新即可

代码:

// Problem: E. Partition Game
// Contest: Codeforces - Codeforces Round #721 (Div. 2)
// URL: https://codeforces.com/contest/1527/problem/E
// Memory Limit: 256 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast","inline","-ffast-math")
// #pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<list>
#include<unordered_map>
#define lowbit(x) x&-x
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
template<typename T>
inline void read(T &x)
{
	T f=1;x=0;
	char ch=getchar();
	while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	x*=f;
}
template<typename T>
inline void write(T x)
{
	if(x<0){x=~(x-1);putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
const int inf=0x3f3f3f3f;
const int N=4e4+100;
struct Node {
	int l,r,mmin,lazy;
}tree[N<<2];
int dp[N],a[N],last[N];;
void pushup(int k) {
	tree[k].mmin=min(tree[k<<1].mmin,tree[k<<1|1].mmin);
}
void pushdown(int k) {
	if(tree[k].lazy) {
		int lz=tree[k].lazy;
		tree[k].lazy=0;
		tree[k<<1].mmin+=lz;
		tree[k<<1|1].mmin+=lz;
		tree[k<<1].lazy+=lz;
		tree[k<<1|1].lazy+=lz;
	}
}
void build(int k,int l,int r) {
	tree[k]={l,r,inf,0};
	if(l==r) {
		tree[k].mmin=dp[l];
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	pushup(k);
}
以上是关于CodeForces - 1527E Partition Game(dp+线段树)的主要内容,如果未能解决你的问题,请参考以下文章

mysql 分区

kafka

磁盘分区模式

算法:快速排序

数据湖:Iceberg特点详述和数据类型

flinksql 实时查询hudi 的数据