2020ICPC·小米 网络选拔赛第一场 E-Phone Network(线段树优化dp)

题目链接:点击这里

题目大意:
给出一个长度为 n n n 的序列,每个点的值 v a l ∈ [ 1 , m ] val \in [1,m] val[1,m] ,题目保证每个值都出现过,对于每一个 i ( 1 ≤ i ≤ m ) i(1\le i\le m) i(1im) 求包含 1 1 1 n n n 所有数的区间的最小长度

题目分析:
同样的思路,我们想考虑一个暴力的 d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 i i i 为起点包含 1 1 1 j j j 所有数字的最短长度,设 j j j 出现的位置依次为 p o s 1 , p o s 2 , . . . , p o s k pos_1,pos_2,...,pos_k pos1,pos2,...,posk ,那么在 [ p o s j − 1 , p o s j ] [pos_{j-1},pos_j] [posj1,posj] 这一段有转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , l a s t [ j ] ) dp[i][j]=max(dp[i][j-1],last[j]) dp[i][j]=max(dp[i][j1],last[j])
最后对于 [ p o s k + 1 , n ] [pos_k+1,n] [posk+1,n] d p [ i ] [ j ] dp[i][j] dp[i][j] 应该都赋值为 i n f inf inf
我们考虑有线段树优化这个式子的转移:
我们由 d p [ i ] [ j ] dp[i][j] dp[i][j] 的意义容易知道其是关于 i i i 单调不下降的,这样在 [ p o s j − 1 + 1 , p o s j ] [pos_{j-1}+1,pos_j] [posj1+1,posj] 这一段上,对 d p [ i ] [ j ] ≤ p j dp[i][j]\le p_j dp[i][j]pj 的一段可以直接用线段树区间修改为 p o s j pos_j posj ,接下来考虑如何维护出 d p [ i ] [ j ] − i + 1 dp[i][j]-i+1 dp[i][j]i+1 取其最小值即为所求答案
d p [ i ] [ j ] ≤ p j dp[i][j]\le p_j dp[i][j]pj 这一段可以利用线段树本来就是二叉树的性质进行二分查找来找到想要修改的区间
维护 d p [ i ] [ j ] − i + 1 dp[i][j]-i+1 dp[i][j]i+1 的最小值是这个题最具技巧性的部分:
假设当前我们要维护区间 [ l , r ] [l,r] [l,r] ,设这段区间维护的答案是 r e s res res ,则有:
r e s = m i n i = l r d p [ i ] [ j ] − i + 1 res=min_{i=l}^rdp[i][j]-i+1 res=mini=lrdp[i][j]i+1
= l a z y − r + 1 =lazy-r+1 =lazyr+1
其中 l a z y lazy lazy 就是最后一次对区间 [ l , r ] [l,r] [l,r] 的赋值(不得不说这个转化真的精妙)

具体细节见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 2e5+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
struct sgt{
	int val,res,r,lazy;
}a[maxn<<2];
vector<int>pos[maxn];
void pushup(int root)
{
	a[root].val = min(a[root<<1].val,a[root<<1|1].val);
	a[root].res = min(a[root<<1].res,a[root<<1|1].res);
}
void pushdown(int root)
{
	if(a[root].lazy)
	{
		int tmp = a[root].lazy;
		a[root].lazy = 0;
		a[root<<1].lazy = a[root<<1|1].lazy = tmp;
		a[root<<1].val = a[root<<1|1].val = tmp;
		a[root<<1].res = tmp-a[root<<1].r+1;
		a[root<<1|1].res = tmp-a[root<<1|1].r+1;
	}
}
void build(int root,int l,int r)
{
	a[root].r = r;a[root].res = inf;
	if(l == r) return ;
	int mid = l+r>>1;
	build(root<<1,l,mid);
	build(root<<1|1,mid+1,r);
}
int find(int root,int l,int r,int ql,int qr)
{
	if(a[root].val > qr || l > qr || r < ql) return -1;
	if(l == r) return l;
	int mid = l+r>>1;
	int res = find(root<<1|1,mid+1,r,ql,qr);
	if(res == -1) res = find(root<<1,l,mid,ql,qr);
	return res;
}
void updat(int root,int l,int r,int ql,int qr,int val)
{
	if(l>qr || r<ql) return ;
	if(l>=ql && r<=qr)
	{
		a[root].val = val;
		a[root].lazy = val;
		a[root].res = val-r+1;
		return ;
	}
	pushdown(root);
	int mid = l+r>>1;
	updat(root<<1,l,mid,ql,qr,val);
	updat(root<<1|1,mid+1,r,ql,qr,val);
	pushup(root);
}
int main()
{
	int n = read(),m = read();
	for(int i = 1;i <= n;i++)
	{
		int num = read();
		pos[num].push_back(i);
	}
	build(1,1,n);
	for(int i = 1;i <= m;i++)
	{
		int pre = 0;
		for(auto now : pos[i])
		{
			int poss = find(1,1,n,pre+1,now);
			if(poss != -1) updat(1,1,n,pre+1,poss,now);
			pre = now;
		}
		if(pos[i].back() < n) updat(1,1,n,pos[i].back()+1,n,inf);
		printf("%d%c",a[1].res,i==n ? '\n' : ' ');
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值