[Codeforces 1221D]Make The Fence Great Again-dp

说在前面

一如既往的菜
只能切水题


题目

codeforces 1227D传送门


解法

对于某个和旁边相同高度的栅栏,我们不需要让它增加很高,只需要让它和旁边的栅栏高度不同就行了
显然一个栅栏的高度增加不会超过2,状压就行了

证明:如果最优方案中,有一个栅栏的高度增加了3,那么必然存在0,1,2中的某个值,将它的高度增加值换成该值后,仍然不与相邻栅栏冲突(因为相邻栅栏高度只有两种,而 Δ = 0 , 1 , 2 \Delta=0,1,2 Δ=0,1,2有三种高度)。所以原来的方案不是最优的,证毕


下面是代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int Q , N , a[300005] , b[300005] ;
long long f[2][27] ;

/*
0  000
1  001
2  010
3  011
4  100
5  101
6  110
7  111
*/

template <typename T>
void smin( T &A , T B ){
	if( A > B ) A = B ;
}

bool state_check( int pos , int s ){
	if( pos == 1 ) return true ;
	int s1 = s % 3 , s2 = ( s / 3 ) % 3 , s3 = ( s / 9 ) % 3 ;

	if( a[pos] + s1 != a[pos-1] + s2 && a[pos-2] + s3 != a[pos-1] + s2 ) return true ;  
	return false ;
}

bool trans_check( int t , int s ){
	int t1 = t % 3 , t2 = ( t / 3 ) % 3 ;
	int s2 = ( s / 3 ) % 3 , s3 = ( s / 9 ) % 3 ;
	if( t1 != s2 || t2 != s3 ) return false ;
	return true ;
}

void solve(){
	memset( f , 0 , sizeof( f ) ) ;
	int pre = 0 , now = 1 ;
	for( int i = 1 ; i <= N ; i ++ ){
		swap( now , pre ) ;
		for( int j = 0 ; j <= 26 ; j ++ ){
			f[now][j] = 1e18 ;
			if( state_check( i , j ) == false ) continue ;
			int delta = ( j % 3 ) * b[i] ;
			for( int k = 0 ; k <= 26 ; k ++ ){
				if( trans_check( k , j ) ) smin( f[now][j] , f[pre][k] + delta ) ;
			}
		}
	} 

	long long ans = 1e18 ;
	for( int i = 0 ; i <= 26 ; i ++ )
		smin( ans , f[now][i] ) ;
	printf( "%lld\n" , ans ) ;
}


int main(){
	scanf( "%d" , &Q ) ;
	while( Q -- ){
		scanf( "%d" , &N ) ;
		for( int i = 1 ; i <= N ; i ++ )
			scanf( "%d%d" , &a[i] , &b[i] ) ;
		a[0] = -1 ;
		solve() ;
	}
}

CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值