[Hackerrank HourRank22] Candy Collection DP+ST表+二分+按位处理思想

题目链接

菜鸡博主昨晚淦了一波HourRank,结果1题AC+1题fst后只剩暴力分+1题暴力,#77滚粗,

收获成就---->菜鸡博主在hackerrank上第一次掉rating,rating从2550掉到了2542 TAT,

                        不过好在rating掉的不多,HackerLevel还没掉^_^.

而三道题中,前2题都比较水,而第三题的idea还是很棒的,那么就来讲讲第3题吧.


题意:N个物品,每个物品有颜色Ci和权值Wi,

            每次操作只能从前到后取连续的若干个物品,

            并且每次取走的物品颜色两两不同,代价为所有物品权值的按位取或的值,

            操作次数不限,问将所有物品取走的最小代价 N<=10^5,Wi<=10^6


题解:   出题人给出的做法是一种很妙的线段树优化dp的idea,但是实现难度相对高,暂时不提;

           下面讲一种ST表+二分的方法,

            首先,这道题一眼就是dp,最sb的方程:dp[i]=min(dp[j-1]+or(j,i)),j=lst[i]....i.

            而方程中lst[i]表示从lst[i]到i没有出现重复颜色的最靠前的位置,

            lst[i]显然是单调的,于是可以利用two pointers思想O(n)求出所有lst[i]的值.

            求出了所有lst[i]的值,但dp还是O(n^2)的,并没有优化,

            于是考虑按位或运算的性质,

            当位置i固定时,or(j,i)的值随着j增加单调不上升,

             而且or(j,i)的总取值数只有O(logWmax)种.

             于是可以对这O(logWmax)种分别计算,

             因为dp数组也是单调不下降的,所以对于or(pos,i)相同的区间[lpos,rpos],

             显然在lpos位置进行dp转移是更优的方案,

             在O(nlogn)时间内预处理出整个Wi的or运算ST表后,

             每次寻找or(pos,i)相同的区间[lpos,rpos]可以利用二分做到O(logn),dp转移可以O(1)实现,

             对于每一个位置,这样的操作最多进行O(logWmax)次,所以总时间复杂度就是O(nlognlogWmax).

             虽然N=5*10^5,但是两个log在hackerrank上还是丝毫不虚的^_^

Code:

#include <bits/stdc++.h>
#define ll long long
#define inf 1000000000000000000ll
using namespace std;
inline int read()
{int x=0;
char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
int n,st[500005][20],logn[500005];
int a[500005],w[500005],lst[500005];
int lar[1000005];ll dp[500005];
inline int get(int a,int b)
{int t=logn[b-a+1];
return (st[a][t]|st[b-(1<<t)+1][t]);
}
int main(){
	int i,j;
	n=read();
	for (i=1;i<=n;i++)
	{a[i]=read();}
	for (i=1;i<=n;i++)
	{lst[i]=max(lst[i-1],lar[a[i]]+1);
	lar[a[i]]=i;
	}
	logn[0]=-1;
	for (i=1;i<=n;i++)
	{w[i]=read();
	logn[i]=logn[i/2]+1;
	st[i][0]=w[i];
	}
	for (j=1;(1<<j)<=n;j++)
	{for (i=1;i+(1<<j)-1<=n;i++)
	{st[i][j]=(st[i][j-1]|st[i+(1<<(j-1))][j-1]);}
	}
	for (i=1;i<=n;i++)
	{dp[i]=inf;
	int lpos=i;
	while (lpos>=lst[i])
	{int l=lst[i],r=lpos;
	while (l<=r)
	{int mid=(l+r)>>1;
	if (get(mid,i)==get(lpos,i))
	{r=mid-1;}
	else
	{l=mid+1;}
	}
	dp[i]=min(dp[i],dp[l-1]+get(l,i));
	lpos=l-1;
	}
	}
	printf ("%lld\n",dp[n]);
	return 0;
}
	
	
	
	


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值