2021-07-29 DP 专练

HDU6212 Zuma

题意简述:给定一个序列代表一行的黑白球,保证开始时没有任何超过两个的同颜色球相邻,你手上有足够多的黑白球,每次可以将其插入到任意位置,当三个及以上的同颜色球排在一起时会被消除,求清空整个序列需要的最少操作数。

区间 DP,区间 DP。

考虑到这是个序列问题,很容易考虑到区间 DP,区间 DP 的核心是大区间的答案通过小区间的答案合并获得,我们考虑 [ L , R ] [L,R] [L,R] 这个区间会怎么被消除。

最暴力也是最行之有效的是直接枚举一个断点 ( L ≤ k ≤ R ) (L \le k \le R) (LkR),分别消除 [ L , k ] [L,k] [L,k] ( k , R ] (k,R] (k,R], 这也是最简单的区间 DP。

但这显然不是所有情况,考虑还有什么情况。

这题的核心问题在于每消除一段颜色,其他的球就会向这里“靠拢”,如果这个“靠拢”没有跨越子区间,那么显然可以有一个更长的区间包含了这次“靠拢”的最优解,这样就还是能通过上面的方程解决问题。

但是有且仅有一些操作"跨越"了子区间时,它不能通过枚举断点得到答案,如图:


在说这幅图之前,先得有一个认识:区间 DP 的正确性是因为也许一个区间的最优解是很多个小区间拼成的,但为什么我们可以两个区间合并得到呢?是因为这些小区间随着区间长度增大逐渐合并了。

上图的情况,简单来说就是它虽然还是两个区间分别求解,而是求解的两个区间并没有完整的覆盖整个答案,而是留下一些同色点“靠拢”,自动消除。基于上述的认识和论证,很容易发现这也是唯一的情况,所以分类讨论一下就好了。

实现的时候,为了方便可以采取类似“缩点”的操作,把同色节点缩成一个,这样一来整个区间就一定是黑点白点交替出现了。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define inf 20000000
using namespace std;
const int maxn=305;
int t,n,a[maxn],b[maxn],c[maxn],tot=0;
char s[maxn];
int dp[2000][2000]; 
int main(){
    
     
    scanf("%d",&t);
    int data_cnt=0,cnt=0;
    while(t--){
    
    
    cnt=tot=0;
    memset(b,0,sizeof(b));
    memset(s,0,sizeof(s));
    for(int i=1;i<=200;i++){
    
    
    	for(int j=i;j<=200;j++){
    
    
    		dp[i][j]=(j-i+1)*2;
		}
	}
    scanf("%s",s+1);
    int n=strlen(s+1);
    for(int i=1;i<=n;i++){
    
    
    	a[i]=s[i]-'0'; 
	}
	for(int i=1;i<=n;){
    
    
		b[++tot]=a[i];
		c[tot]=i;
		while(i<=n){
    
    
			if(a[i]!=b[tot])
			break;
			i++;
		}
		c[tot]=i-c[tot]; 
	}
	for(int i=1;i<=tot;i++){
    
    
		if(c[i]==2)
		dp[i][i]=1;
		else
		dp[i][i]=2;
	}
	for(int i=1;i<=tot-1;i++){
    
    
        for(int j=1;j<=tot-i;j++){
    
    
            for(int k=j+1;k<=j+i;k++){
    
    
            	dp[j][i+j]=min(dp[j][i+j],dp[j][k-1]+dp[k][i+j]);	
			}
			 if((i)&1)
			continue;
            dp[j][i+j]=min(dp[j][i+j],dp[j+1][i+j-1]+(c[j]+c[j+i]==2));
            if(c[j]+c[j+i]<=3){
    
    
                for(int k=j+2;k<i+j;k+=2){
    
    
                    if(c[k]!=1)
					continue;
					dp[j][i+j]=min(dp[j][i+j],dp[j+1][k-1]+dp[k+1][i+j-1]); 
            	}
            } 
		}
	}
	printf("Case #%d: %d\n",++data_cnt,dp[1][tot]);
   	}      
    return 0;
}

Necklace

简述题意:给定一个有 n n n 个元素的环,其都有一个值 a i a_i ai,删去一些元素使得剩下的元素存在一种顺序可以以一个 a i = 10000 a_i=10000 ai=10000 分界,左边的数的单调不增,右边的数单调不减。

简单 DP+线段树,显然对于一个环套路的破环成链,对于每个 a i = 10000 a_i=10000 ai=10000 ,对于左边右边分别跑稍微改一下的权值最大上升/下降子串,答案取最大值。

然后这样的复杂度显然不能过题,我们把这东西转移方程写出来:

d p i = ∑ max ⁡ d p j + a i ( a j ≤ a i ) dp_i=\sum \max dp_j+a_i (a_j \le a_i) dpi=maxdpj+ai(ajai)

那么 a i a_i ai 是定值,区间 max 是老传统艺能了,直接线段树/树状数组等数据结构就好了。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define inf 200000000
using namespace std;
const int maxn=5e4;
const int maxr=1e4;
int n,ls[maxn],rs[maxn],v[maxn],a[maxn*10],f1[maxn*10],f2[maxn*10];
inline void push_up(int i){
    
    
	v[i]=max(v[ls[i]],v[rs[i]]);
}
inline void build(int i,int l,int r){
    
    
	if(l==r) return ;
	int mid=(l+r)/2;
	ls[i]=i*2;
	rs[i]=i*2+1;
	build(ls[i],l,mid);
	build(rs[i],mid+1,r);
	push_up(i);
}
inline int query(int i,int l,int r,int L,int R){
    
    
	if(L<=l&&r<=R){
    
    
		return v[i];
	}
	int ans=0;
	int mid=(l+r)/2;
	if(L<=mid){
    
    
		ans=max(ans,query(ls[i],l,mid,L,R));
	}
	if(R>mid){
    
    
		ans=max(ans,query(rs[i],mid+1,r,L,R));
	}
	return ans;
}
inline void add(int i,int l,int r,int k,int t){
    
    
	if(l==r){
    
    
		v[i]=t;
		return ;
	}
	int mid=(l+r)/2;
	if(k<=mid){
    
    
		add(ls[i],l,mid,k,t);
	}
	else{
    
    
		add(rs[i],mid+1,r,k,t);
	}	
	push_up(i);
}
int main(){
    
    	
	while(scanf("%d",&n)==1){
    
    
		for(int i=1;i<=n;i++){
    
    
			scanf("%d",&a[i]);
			a[n+i]=a[i];
		}
		int ans=0;
		for(int i=1;i<=n;i++){
    
    
			if(a[i]==maxr){
    
    
				memset(v,0,sizeof(v));
				memset(f1,0,sizeof(f1));
				memset(f2,0,sizeof(f2));
				build(1,0,maxr);
				for(int j=i+1;j<i+n;j++){
    
    
					if(a[j]==maxr){
    
    
						f1[j]=max(f1[j],f1[j-1]); 
						continue;
					}
					f1[j]=a[j]+query(1,0,maxr,a[j],maxr);
					add(1,0,maxr,a[j],f1[j]);
					f1[j]=max(f1[j],f1[j-1]);
				}
				memset(v,0,sizeof(v));
				build(1,0,maxr);
				for(int j=i+n-1;j>i;j--){
    
    
					if(a[j]==maxr){
    
    
						f2[j]=max(f2[j],f2[j+1]); 
						continue;
					}
					f2[j]=a[j]+query(1,0,maxr,a[j],maxr);
					add(1,0,maxr,a[j],f2[j]);
					f2[j]=max(f2[j],f2[j+1]);
				}
				for(int j=i;j<i+n;j++){
    
    
					ans=max(ans,f1[j]+f2[j+1]+maxr);
				}
			}			
		}
		printf("%d\n",ans);
	}
}

Guess you like

Origin blog.csdn.net/cryozwq/article/details/119207353
DP