Codeforces Round #721 (Div. 2) E - Partition Game

E - Partition Game
题意:给一个长度为n的数组,将其分成k段,每段的权值定义为这段数组中每个不同的数的cost和,对于一个数的cost为这个数在这一段中最后一次出现和第一次出现的距离差。求分成连续k段的最小权值和。

昨天打cf看了一下这个题,和银川B好像,于是就想能不能巧妙的dp一下,想了挺长时间也没有dp的思路,感觉现在这种题就像我的梦魇似的,银川B题因为比赛前一天看了很多数据结构的题导致完全没想dp,而这个题又因为银川B题的折磨也没想数据结构,真的感觉现在处于一个知识量和思维和做题状态完全不匹配的一个阶段,银川B就因为完全不会斜率dp导致不敢再用其他的方法想,而且知道有很多方向但是因为很久没训练导致想明白一个问题需要很长时间。按理说确实应该退役,但是对金牌执念太大了,还是想再冲一冲,尽量补一补cf的题保持一下状态吧,但愿老赵和马哥保研成功能带我飞。

思路:
dp[i][j]代表前i个数分成j段的最小权值和。
转移就是dp[i][j]=min(dp[k][j-1]+val(k+1,i)),k为0到i-1.
然后考虑怎么维护
明显dp[i][j]只与dp[i][j-1]有关,所以可以把j放在外面当做第一层循环,
这样转移式的dp[k][j-1]
然后维护的时候假设枚举到了第i个数为最后一个数,那么找到这个数前面第一个为a[i]的数的位置p,对于[1,p-1]区间+(i-p)即可。
有些边界和INF细节处理一下就ok了

#include<iostream>
#include<cstdio>
using namespace std;
const int MAX_N=35010;
const int INF=0x3f3f3f3f;
struct node{
 	int l,r,minl,lazy;
}a[MAX_N*4];
int dp[MAX_N],b[MAX_N];
void update(int k){
 	//a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
 	a[k].minl=min(a[k<<1].minl,a[k<<1|1].minl);
}
void build(int k,int l,int r){
 	a[k].l=l;a[k].r=r;a[k].lazy=0;
 	if(l==r){
  		a[k].minl=dp[l];
  		return;
 	}
 	int mid=(l+r)>>1;
 	build(k<<1,l,mid);
 	build(k<<1|1,mid+1,r);
 	update(k);
}
void pushdown(int k){
 	if(a[k].lazy==0)
 	return;
 	a[k<<1].lazy+=a[k].lazy;
	a[k<<1|1].lazy+=a[k].lazy;
 	a[k<<1].minl+=a[k].lazy;
 	a[k<<1|1].minl+=a[k].lazy;
 	a[k].lazy=0;
}
void changeS(int k,int l,int r,int y){
 	if(a[k].l>=l&&a[k].r<=r){
  		a[k].minl+=y;
  		a[k].lazy+=y;
  		return;
 	}
 	pushdown(k);
 	int mid=(a[k].l+a[k].r)>>1;
 	if(l<=mid)
 	changeS(k<<1,l,r,y);
 	if(r>mid)
 	changeS(k<<1|1,l,r,y);
 	update(k);
}
int query(int k,int l,int r){	
 	if(a[k].l>=l&&a[k].r<=r)
 	return a[k].minl;
 	pushdown(k);
 	int mid=(a[k].l+a[k].r)>>1;
 	int res=INF;
 	if(l<=mid)
 	res=min(res,query(k<<1,l,r));
 	if(r>mid)
 	res=min(res,query(k<<1|1,l,r));
 	return res;
}
int rk[MAX_N],pre[MAX_N];
int main(void){
	int n,k,i,j;
	scanf("%d%d",&n,&k);
	for(i=1;i<=n;i++)
	scanf("%d",&b[i]);
	for(i=1;i<=n;i++){
		pre[i]=rk[b[i]];
		rk[b[i]]=i;
	}
	for(j=1;j<=n;j++)
	dp[j]=INF;
	for(i=1;i<=k;i++){
		for(j=0;j<i-1;j++)
		dp[j]=INF;
		build(1,0,n);
		for(j=i;j<=n;j++){
			int val=j-pre[j];
			if(pre[j]==0)
			val=0;
			//cout<<pre[j]-1<<" "<<val<<" !!!\n";
			if(pre[j])
			changeS(1,0,pre[j]-1,val);
			dp[j]=query(1,0,j-1);
			//changeS(1,j,j,dp[j]); 
		}
//		for(j=0;j<=n;j++)
//		cout<<dp[j]<<" ";
//		cout<<"\n";
	}
	printf("%d\n",dp[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值