【题解】CF82D Two out of Three

给定一个序列\(a_i\),每次可以删除前\(3\)个中的\(2\)个,代价为所删两数的\(a_i\)最大值;若数字个数小于\(3\),就一次删完,代价同样为\(a_i\)最大值。求删掉所有数的最小代价以及方案。

\(n\le 1000,1\le a_i\le 10^6\)

Solution

这样的删数方式很特别,我们需要寻找一些性质。

从序列的形状上看,没有被删的数应该是 一个单点+一个后缀,也可能只有一个后缀。

啥,你说咋看出来的?后缀显然,单点多删几次就可以发现。

那不就很好dp了吗。

设\(f_{i,j}\)表示单点位置为\(i\),后缀左端点为\(j\)时,最小代价是多少。

刷表,讨论删掉前\(3\)个中的哪\(2\)个。

\(f_{i,j}\)可以贡献到:

  • \(f_{i,j+2}\)
  • \(f_{j+1,j+1}\)
  • \(f_{j,j+2}\)

还要考虑只有后缀的情况,不妨设当\(i=j\)时,后缀为从\(i\)开始的区间。
\(f_{i,i}\)可以贡献到

  • \(f_{i+2,i+2}\)
  • \(f_{i+1,i+3}\)
  • \(f_{i,i+3}\)

然后就是记录方案。

代码:

#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
inline int read(){
	int x=0,f=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') f|=(ch=='-'),ch=getchar();
	while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
typedef pair<int,int> pii;
const int N=1005;
int n,a[N],f[N][N],pre[N][N][2];
void upd(int x,int y,int i,int j,int val){
	//用f[i,j]更新f[x,y],val为转移时的代价 
	if(f[x][y]>f[i][j]+val){
		f[x][y]=f[i][j]+val;
		pre[x][y][0]=i;
		pre[x][y][1]=j;
	}
}
int main(){
	n=read();
	for(int i=1;i<=n;++i) a[i]=read();
	memset(f,0x3f,sizeof(f));
	f[1][1]=0;
	for(int i=1;i<=n+2;++i){
		upd(i+2,i+2,i,i,max(a[i],a[i+1]));
		upd(i+1,i+3,i,i,max(a[i],a[i+2]));
		upd(i,i+3,i,i,max(a[i+1],a[i+2]));
		for(int j=i+1;j<=n+2;++j){
			upd(i,j+2,i,j,max(a[j],a[j+1]));
			upd(j+1,j+1,i,j,max(a[i],a[j]));
			upd(j,j+2,i,j,max(a[i],a[j+1])); 
		}
	}
	vector<pii>ans;
	int x,y;
	if(n&1) printf("%d\n",f[n+2][n+2]),x=y=n+2;
	else printf("%d\n",f[n+1][n+1]),x=y=n+1;
	while(x>1||y>1){
		int px=pre[x][y][0],py=pre[x][y][1];
		if(px==py){
			if(x==px+2&&y==py+2) ans.pb(mp(px,px+1));
			if(x==px+1&&y==py+3) ans.pb(mp(px,px+2));
			if(x==px&&y==py+3) ans.pb(mp(px+1,px+2));
		}else{
			if(x==py+1&&y==py+1) ans.pb(mp(px,py));
			if(x==py&&y==py+2) ans.pb(mp(px,py+1));
			if(x==px&&y==py+2) ans.pb(mp(py,py+1));
		}
		x=px;
		y=py;
	}
	reverse(ans.begin(),ans.end());
	for(int i=0;i<ans.size();++i){
//		printf("%d %d\n",ans[i].fi,ans[i].se);
		if(ans[i].fi<=n) printf("%d ",ans[i].fi);
		if(ans[i].se<=n) printf("%d ",ans[i].se);
		printf("\n");
	}
	return 0;
}
/*
给定一个序列,每次可以删除前3个中的2个,代价为max(ai),剩余一个时代价为a,求最小代价 

剩下的元素是一个单点 & 一个后缀

f[i,j]:单点在i,后缀左端点为j,最小时间为多少

f[i,i] -> f[i+2,i+2],f[i+1,i+3],f[i,i+3]
f[i,j] -> f[i,j+2],f[j+1,j+1],f[j,j+2]
*/
上一篇:python 装饰器笔记


下一篇:大爽Python入门练习题 2-6 两数之和(Two Sum)