[dp] 对hdu4747 Mex 的详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq1965610770/article/details/80041940

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4747

题目大意:

给一个含有n个数的序列 ns[1~n]

定义函数 mex(l,r)为区间 [l,r] 中未出现的最小的非负整数

求序列ns所有区间的 mex 和

可以表示为:

for( l=1 ; l<=n ; l++)
	for( r=l ; r<=n ; r++)
		sum += mex( l , r );

解题思路:

这道题除了暴力之外,我的其他思路都是直接参考其他博客大神的

有两种思路:

    一种是线段树思路,来源https://blog.csdn.net/acm_cxlove/article/details/11749383

    一种是递推计数思路,来源https://blog.csdn.net/cc_again/article/details/11856847

首先是比较简单一点的线段树思路:

       先构建一个数组 mex_i[i] = mex(1,i),

       之后每删除最左边的节点(ns[i]),更新mex_i区间 [i,j] 中比 ns[i] 大的值为 ns[i]

       其中 i 表示现在遍历到(删除)的节点, j 表示为数 ns[i] 下次出现的位置

       用线段树维护 值修改 与 区间求和 的步骤

具体代码可见上面的链接


之后是递推计数的思路,这个思路比较懵,我实在不知道那些大神是怎么想出来那么凶残的方法的,之后详解:

       首先是有两个数组,pos 与 full

       其中 pos[i] 表示值 i 前一次出现的位置,full[j] 表示区间[x,i] 已经覆盖(出现)了0~j这些数,中x的最大值(最右)

       而 tt(i) 表示为对目前的 i,mex(1 , i)~mex(i , i) 的和,注意是 mex(1 , i)~mex(i , i) ,再累加进 ans 中

       需要操作的区间为(last,i ],因为 ns[i] 的出现对last之前的 区域无影响

       而 full[j] = min( full[j-1] , pos[j] ) 可解释为更新的 full[i]的值 为 full[i-1] 与 pos[j] 的较小值(靠左端)

       保证了区间 [full[j] , i] 满足已经覆盖(出现)了0~j 这几个数,而 mex[last+1 , i]~ mex[full[j] , i] 的保底值为j+1

       所以在这个区间累加上去,而对任意mex(j , i),保证有mex(j-1 , i) >= mex(j , i),由此可以逐步累加上去

       而在同一个阶段,也可以保证 full[j] <= full[j-1],由此可以将它 break 掉

之后该思路带注释的代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
int ns[200010];		// 序列 
int pos[200010];	// pos[i] 表示现阶段 i 在序列 ns 中出现的最后一个位置 
int full[200010];	// 现在遍历到了 i ,full[j] 表示已经覆盖0~j的区间[x,i] 中 x 的最大值 
int main()
{
	int n;
	while(~scanf("%d",&n) && n)
	{
		int i,j;
		for(i=1;i<=n;i++)
			scanf("%d",&ns[i]);
		memset(pos,0,sizeof(pos));
		memset(full,0,sizeof(full));
		int last;
		ll tt=0,ans=0;
		for(i=1;i<=n;i++)
		{
			if(ns[i] < n)	//对长度为n的序列无影响 
			{
				last = pos[ns[i]];	//数 ns[i] 前一次出现的最后位置 
				pos[ns[i]] = i;		//更新 ns[i] 最后的位置 
				for(j=ns[i];j<n;j++)	//遍历会产生影响的数字 
				{
					if(j)	full[j] = min(full[j-1],pos[j]);	//更新full[j] 为 full[j-1] 与 pos[i] 中较小(左)的位置 
					else	full[j] = i;	//如果是 0 ,则更新 full[0] 为现在这个 ns[i]=0 的位置i 
					if(full[j] > last)
						tt += full[j]-last;	//累加对last之前的位置无影响 
					else
						break;
				}
			}
			ans += tt;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq1965610770/article/details/80041940
Mex