区间dp杂题

区间dp杂题

P2734 [USACO3.3] 游戏 A Game

我们设置 \(f[i][j]\) 表示 \([i,j]\) 区间内先手的最大分数

对于这个区间内的先手 可以取左面一个 也可以取右面一个

那么他在 \([i+1,j]\)\([i,j-1]\) 区间内就变成了后手

\(f[l][r] = max ( a[l] + sum[r] - sum[l] - f[l+1][r] , a[r] + sum[r-1] - sum[l-1] - f[l][r-1] )\)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define pii pair<int,int>
#define print(x) cerr<<#x<<'='<<x<<endl
const double inf = 2e9 + 5;
const int N = 100 + 5;
// #define getchar() cin.get()
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , f[N][N] , sum[N] , a[N];//f[i][j] 表示i到j中 作为先手的最大价值

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i=-~i ) a[i] = read() , sum[i] = sum[i-1] + a[i] , f[i][i] = a[i];
	for ( int k = 2 ; k <= n ; k=-~k )
		for ( int l = 1 , r = l + k - 1 ; r <= n ; l =-~l , r =-~r )
			f[l][r] = max ( a[l] + sum[r] - sum[l] - f[l+1][r] , a[r] + sum[r-1] - sum[l-1] - f[l][r-1] );
	cout << f[1][n] << ' ' << sum[n] - f[1][n] << endl;
	return 0;
}

P2858 [USACO06FEB] Treats for the Cows G/S

\(naive\) 的区间 \(dp\)

设置 \(f[l][r]\) 表示 \(l\)\(r\) 区间内的最大取数价值

那么初始值显然有 \(f[i][i]=a[i]\)

\(f[l][r]=max ( f[l][r-1] + a[r] * ( n - k + 1 ) , f[l+1][r] + a[l] * ( n - k + 1 ) )\) ( \(k\) 为区间长度 )

我们区间长度为 \(1\) 的时候相当于乘上了 \(n-1+1\) 那么我们区间长度为 \(k\) 的时候相当于乘上了 \(n-k+1\)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
const int N = 2e3 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , f[N][N] , a[N];//总价值为i 选了j个物品的重要度总和最大值

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , f[i][i] = a[i] * n;
	for ( int k = 2 ; k <= n ; k ++ )
		for ( int l = 1 , r = l + k - 1 ; r <= n ; l ++ , r ++ )
			f[l][r] = max ( f[l][r-1] + a[r] * ( n - k + 1 ) , f[l+1][r] + a[l] * ( n - k + 1 ) );
	cout << f[1][n] << endl;
	return 0;
}

P1005 [NOIP2007 提高组] 矩阵取数游戏

上一道题稍微改一改即可

需要开 \(\_ \_ int128\)

#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
#define inl inline
#define eb emplace_back
#define endl '\n'
#define ls p<<1
#define rs p<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define print(x) cerr<<#x<<'='<<x<<endl
#define getchar() cin.get()
#define int __int128
const int N = 80 + 5;
const int inf = 2e9 + 5;
int read()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

void write ( int x )
{
	if ( x < 0 ) cout.put('-') , x = -x;
	if ( x > 9 ) write ( x / 10 );
	cout.put ( x % 10 + '0' );
}

int n , m , f[N][N][N] , a[N][N] , sum;//总价值为i 选了j个物品的重要度总和最大值

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ )
		 for ( int j = 1 ; j <= m ; j ++ )
			a[i][j] = read() , f[i][j][j] = a[i][j] * ( (int)1 << m );
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int k = 2 ; k <= m ; k ++ )
			for ( int l = 1 , r = l + k - 1 ; r <= m ; l ++ , r ++ )
				f[i][l][r] = max ( f[i][l][r-1] + a[i][r] * ( (int)1 << (m-k+1) ) , f[i][l+1][r] + a[i][l] * ( (int)1 << (m-k+1) ) );
	for ( int i = 1 ; i <= n ; i ++ ) sum += f[i][1][m];
	write(sum);
	return 0;
}

P3205 [HNOI2010] 合唱队

我们相当于是对于一个最终序列 \(dp\) 找出全部的合法初始序列(即将最终序列合法地选取左或者右来实现)

\(f[l][r][0]\) 表示处理 \([l,r]\) 区间 最后一个人是 \(l\) 从左面进入 \(f[l][r][1]\) 表示最后一个人是 \(r\) 从右面进入

那么转移方程即为:

\(f[i][j][0]+=f[i+1][j][0](a[i+1] > a[i])\)

\(f[i][j][1]+=f[i+1][j][0](a[j] > a[i])\)

\(f[i][j][1] += f[i][j-1][0]( a[i] < a[j] )\)

\(f[i][j][1] += f[i][j-1][1](a[j-1] < a[j])\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define getchar() cin.get()
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 19650827;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , k , dis[N] , f[N][N][2] , a[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	for ( int i = 1 ; i <= n ; i ++ ) f[i][i][0] = 1;
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int i = 1 , j = i + len - 1 ; j <= n ; i ++ , j ++ )
		{
			if ( a[i] < a[j] ) ( f[i][j][1] += f[i][j-1][0] ) %= mod;
			if ( a[j-1] < a[j] ) ( f[i][j][1] += f[i][j-1][1] ) %= mod;
			if ( a[i+1] > a[i] ) ( f[i][j][0] += f[i+1][j][0] ) %= mod;
			if ( a[j] > a[i] ) ( f[i][j][0] += f[i+1][j][1] ) %= mod;
		}
	cout << ( f[1][n][0] + f[1][n][1] ) % mod << endl;
	return 0;
}

P4290 [HAOI2008] 玩具取名

\(f[l][r][1/2/3/4]\) 表示 \([l,r]\) 区间 能否用 \(WING\) 四个字母分别生成出来

先用 \(can\) 数组记录字母之间的生成关系 再进行转移即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define getchar() cin.get()
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 200 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}


int n , f[N][N][5] , num[5] , can[5][5][5];

int change ( char ch )
{
	if ( ch == 'W' ) return 1;
	if ( ch == 'I' ) return 2;
	if ( ch == 'N' ) return 3;
	if ( ch == 'G' ) return 4;
}

string s;
char ch1 , ch2;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	generate ( num + 1 , num + 4 + 1 , read );
	for ( int i = 1 ; i <= 4 ; i ++ )
		for ( int j = 1 ; j <= num[i] ; j ++ )
		{
			cin >> ch1 >> ch2;
			can[i][change(ch1)][change(ch2)] = 1;
		}
	cin >> s , n = s.size() , s = " " + s;
	for ( int i = 1 ; i <= n ; i ++ ) f[i][i][change(s[i])] = 1;
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
			for ( int k = l ; k < r ; k ++ ) 
				for ( int k0 = 1 ; k0 <= 4 ; k0 ++ )
					for ( int k1 = 1 ; k1 <= 4 ; k1 ++ )
						for ( int k2 = 1 ; k2 <= 4 ; k2 ++ )
							if ( can[k0][k1][k2] && f[l][k][k1] && f[k+1][r][k2] )
								f[l][r][k0] = 1;
	int flag = 0;
	if ( f[1][n][1] ) cout << "W" , flag = 1;
	if ( f[1][n][2] ) cout << "I" , flag = 1;
	if ( f[1][n][3] ) cout << "N" , flag = 1;
	if ( f[1][n][4] ) cout << "G" , flag = 1;
	if ( !flag ) cout << "The name is wrong!" << endl;
	return 0;
}

P6701 [POI1997] Genotype

和上一道的思维有相似之处

分裂很困难 所以我们考虑合并


P3146 [USACO16OPEN] 248 G

显然我们可以设置 \(f[l][r]\) 表示 \([l,r]\) 区间内能拼出的最大数 枚举 \(len\) \(l\) \(r\) 断点 \(k\) 即可转移 时间复杂度 \(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define getchar() cin.get()
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
const int N = 250 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}


int n , f[N][N] , ans;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) f[i][i] = read() , ans = max ( ans , f[i][i] );
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
			for ( int k = l ; k < r ; k ++ )
				if ( f[l][k] == f[k+1][r] && f[l][k] && f[k+1][r] ) 
					f[l][r] = max ( f[l][r] , f[l][k] + 1 ) , ans = max ( ans , f[l][r] );
	cout << ans << endl;
	return 0;
}

P3147 [USACO16OPEN] 262144 P

上一道题的优化 \(version\)

\(f[i][j]\) 表示以 \(j\) 为左端点的区间 能凑出 \(i\) 这个数的右端点的右面一个的位置

那么转移方程有 \(f[i][j]=f[i-1][f[i-1][j]]\) 如果 \(f\) 数组中有值 那么显然 \(f[i][j]\) 所对应的 \(i\) 值是合法能被凑出来的

时间复杂度 \(O(n(logn+40))\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
const int N = ( 1 << 18 ) + 5;
const int M = 58 + 5;//40+logn

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}


int n , f[M][N] , ans;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) f[read()][i] = i + 1;
	for ( int i = 2 ; i <= 58 ; i ++ )
		for ( int j = 1 ; j <= n ; j ++ )
		{
			if ( !f[i][j] ) f[i][j] = f[i-1][f[i-1][j]];
			if ( f[i][j] ) ans = i;
		}
	cout << ans << endl;
	return 0;
}

P1220 关路灯

经典的一类"当前决策会影响未来价值"的区间 \(dp\) 问题 那么对于这类问题 我们在每次转移的时候 除了累加当次权值之外 还需要将对未来的影响累加进去

那么设置 \(f[l][r][0]\) 表示我们消除 \([l,r]\) 区间内的所有灯 最后一次关的是最左面的 \(i\) 号灯的最小花费

\(f[l][r][1]\) 表示消除 \([l,r]\) 区间内的所有灯 最后一次关的是最右面的 \(j\) 号灯的最小花费

记录前缀和并进行转移即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
const int N = 50 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , f[N][N][2] , a[N] , w[N] , sum[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , w[i] = read();
	partial_sum ( w + 1 , w + n + 1 , sum + 1 );
	memset ( f , inf , sizeof f );
	f[m][m][0] = f[m][m][1] = 0;
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
		{
			f[l][r][0] = min ( f[l+1][r][0] + ( a[l+1] - a[l] ) * ( sum[l] + ( sum[n] - sum[r] ) ) , f[l+1][r][1] + ( a[r] - a[l] ) * ( sum[l] + ( sum[n] - sum[r] ) ) );
			f[l][r][1] = min ( f[l][r-1][0] + ( a[r] - a[l] ) * ( sum[l-1] + ( sum[n] - sum[r-1] ) ) , f[l][r-1][1] + ( a[r] - a[r-1] ) * ( sum[l-1] + ( sum[n] - sum[r-1] ) ) ); 
		}
	cout << min ( f[1][n][0] , f[1][n][1] ) << endl;
	return 0;
}

P2466 [SDOI2008] Sue 的小球

和上一道题有异曲同工之妙 需要开 \(long\ long\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int N = 1000 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , f[N][N][2] , sum[N] , res;

struct node { int x , y , v; } a[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].x = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i].y = read() , res += a[i].y;
	for ( int i = 1 ; i <= n ; i ++ ) a[i].v = read();
	a[++n] = { m , 0 , 0 };
	memset ( f , -inf , sizeof f );
	sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.x < b.x; } );
	for ( int i = 1 ; i <= n ; i ++ ) sum[i] = sum[i-1] + a[i].v;
	for ( int i = 1 ; i <= n ; i ++ ) if ( a[i].x == m ) { m = i; break; }
	f[m][m][0] = f[m][m][1] = res;
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
		{
			f[l][r][0] = max ( f[l+1][r][0] - ( a[l+1].x - a[l].x ) * ( sum[l] + sum[n] - sum[r] ) , f[l+1][r][1] - ( a[r].x - a[l].x ) * ( sum[l] + sum[n] - sum[r] ) );
			f[l][r][1] = max ( f[l][r-1][0] - ( a[r].x - a[l].x ) * ( sum[l-1] + sum[n] - sum[r-1] ) , f[l][r-1][1] - ( a[r].x - a[r-1].x ) * ( sum[l-1] + sum[n] - sum[r-1] ) );
		}
	cout << fixed << setprecision(3) << max ( f[1][n][0] , f[1][n][1] ) / 1000.0 << endl;
	return 0;
}

P4870 [BalticOI 2009 Day1] 甲虫

不同于上一个题 我们这次的题露水不会变成负数 那么我们可以考虑枚举喝露水的数量再用上面的方法进行处理

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int N = 300 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , a[N] , flag , pos , f[N][N][2] , sum[N] , ans;


int solve ( int lim )
{
	memset ( f , inf , sizeof f );
	int res = inf;
	f[pos][pos][0] = f[pos][pos][1] = 0;
	for ( int len = 2 ; len <= lim ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
		{
			f[l][r][0] = min ( f[l+1][r][0] + ( a[l+1] - a[l] ) * ( lim - len + 1 ) , f[l+1][r][1] + ( a[r] - a[l] ) * ( lim - len + 1 ) );
			f[l][r][1] = min ( f[l][r-1][0] + ( a[r] - a[l] ) * ( lim - len + 1 ) , f[l][r-1][1] + ( a[r] - a[r-1] ) * ( lim - len + 1 ) );
			if ( len == lim ) res = min ( res , min ( f[l][r][0] , f[l][r][1] ) );
		}
	return res;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , flag |= (!a[i]);
	if ( !flag ) ++ n;
	sort ( a + 1 , a + n + 1 );
	for ( int i = 1 ; i <= n ; i ++ ) if ( !a[i] ) { pos = i; break; }
	for ( int i = 1 ; i <= n ; i ++ ) ans = max ( ans , i * m - solve(i) );
	cout << max ( 0ll , ans - ( !flag ? m : 0 ) ) << endl;
	return 0;
}

P9119 [春季测试 2023] 圣诞树

首先我们找到第一个要输出的 \(k\) 点 然后以 \(k\) 作为分界 重新将所有节点按照顺时针编号 \(1-(n-1)\)

\(f[l][r][0]\) 表示我们现在处理 \([l,r]\) 区间 最后线到了最左面 \(l\)\(f[l][r][1]\) 同理

转移即可 对于方案的输出 我们在转移同时记录前置状态即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define double long double
#define int long long
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , f[N][N][2] , k = 1 , pre[N][N][2];

struct node { double x , y; int id; } a[N] , tmp[N];

double dis ( int i , int j ) { return sqrtl ( ( a[i].x - a[j].x ) * ( a[i].x - a[j].x ) + ( a[i].y - a[j].y ) * ( a[i].y - a[j].y ) ); } 

void print ( int i , int j , int k )
{
	if ( i == j ) return cout << a[i].id << ' ' , void();
	if ( k ) cout << a[j].id << ' ' , print ( i , j - 1 , pre[i][j][k] );
	else cout << a[i].id << ' ' , print ( i + 1 , j , pre[i][j][k] );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) cin >> a[i].x >> a[i].y , a[i].id = i , tmp[i] = a[i];
	for ( int i = 1 ; i <= n ; i ++ ) if ( a[i].y > a[k].y ) k = i;
	for ( int i = 1 ; i <= k ; i ++ ) a[i+n-k] = tmp[i];
	for ( int i = k + 1 ; i <= n ; i ++ ) a[i-k] = tmp[i];
	for ( int len = 2 ; len < n ; len ++ )
		for ( int i = 1 , j = i + len - 1 ; j < n ; i ++ , j ++ )
		{
			f[i][j][0] = f[i][j][1] = inf;
			if ( f[i][j][0] > f[i+1][j][0] + dis ( i , i + 1 ) ) f[i][j][0] = f[i+1][j][0] + dis ( i , i + 1 ) , pre[i][j][0] = 0;
			if ( f[i][j][0] > f[i+1][j][1] + dis ( i , j ) ) f[i][j][0] = f[i+1][j][1] + dis ( i , j ) , pre[i][j][0] = 1;
			if ( f[i][j][1] > f[i][j-1][0] + dis ( i , j ) ) f[i][j][1] = f[i][j-1][0] + dis ( i , j ) , pre[i][j][1] = 0;
			if ( f[i][j][1] > f[i][j-1][1] + dis ( j - 1 , j ) ) f[i][j][1] = f[i][j-1][1] + dis ( j - 1 , j ) , pre[i][j][1] = 1;
		}
	cout << a[n].id << ' ';
	if ( f[1][n-1][0] + dis ( 1 , n ) > f[1][n-1][1] + dis ( n - 1 , n ) ) print ( 1 , n - 1 , 1 );
	else print ( 1 , n - 1 , 0 );
	return 0;
}

P8675 [蓝桥杯 2018 国 B] 搭积木

调一大顿最后没加上什么都不放的 \(1\) 然后交上去还没开 \(ll\) \(wssb\)

我们设置 \(f[i][l][r]\) 表示 \(dp\) 到第 \(i\) 行 最顶一层的状态为 \([l,r]\) 的方案数

因为每次转移加的时候使用的状态是一个矩形 那么显然我们可以用前缀和优化转移

答案就是所有的合法状态加和再加上 \(1\) 注意 \(long\ long\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
#define int long long
const int N = 1e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , ans = 1 , f[N][N][N] , sum[N][N] , a[N][N];

char ch;

int query ( int lx , int ly , int rx , int ry )
{
	return ( sum[rx][ry] + sum[lx-1][ly-1] - sum[rx][ly-1] - sum[lx-1][ry] ) % mod;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 ; j <= m ; j ++ )
			cin >> ch , a[i][j] = a[i][j-1] + ( ch == 'X' );
	for ( int i = 1 ; i <= m ; i ++ )
		for ( int j = i ; j <= m ; j ++ )
			f[n][i][j] = ( a[n][j] - a[n][i-1] == 0 );
	for ( int i = n - 1 ; i ; i -- )
	{
		for ( int j = 1 ; j <= m ; j ++ ) 
			for ( int k = j ; k <= m ; k ++ )
				( sum[j][k] = sum[j-1][k] + sum[j][k-1] - sum[j-1][k-1] + f[i+1][j][k] ) %= mod;
		for ( int j = 1 ; j <= m ; j ++ ) 
			for ( int k = j ; k <= m ; k ++ )
				if ( a[i][k] - a[i][j-1] == 0 )
					( f[i][j][k] += query ( 1 , k , j , m ) ) %= mod;
	}
	for ( int i = 1 ; i <= n ; i ++ )
		for ( int j = 1 ; j <= m ; j ++ ) 
			for ( int k = j ; k <= m ; k ++ )	
				( ans += f[i][j][k] ) %= mod;
	cout << ans << endl;
	return 0;
}

P5851 [USACO19DEC] Greedy Pie Eaters P

考虑设 \(f[l][r]\) 为答案 \(g[l][r][k]\) 表示 \([l,r]\) 区间内 吃掉 \(k\) 的最重的牛(奶牛吃的区间一定在 \([l,r]\) 之间)

我们知道 \(dp\) 一般是考虑最后一步和上一步的关系 对于区间 \([i,j]\) 我们吃掉的最后一个派如果是派 \(k\) 那么其他 \([l,k-1]\)\([k+1,r]\) 两个区间是对于这头牛没用的 因为一定剩了一个派给这个牛吃 且左右两个区间因为区间不够大 不会使用到当前这头牛

那么对于 \(g\) 的预处理 我们可以直接从每一个点为中心开始枚举所有区间 对于一头牛 只要有能覆盖这个区间的区间 我们就进行覆盖即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
const int N = 300 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , f[N][N] , g[N][N][N];

char ch;

struct node { double x , y; int id; } a[N] , tmp[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	for ( int i = 1 ; i <= m ; i ++ ) 
	{
		int w = read() , l = read() , r = read();
		for ( int j = l ; j <= r ; j ++ ) g[l][r][j] = w;
	}
	for ( int k = 1 ; k <= n ; k ++ )
		for ( int i = k ; i ; i -- )
			for ( int j = k ; j <= n ; j ++ )
				g[i-1][j][k] = max ( g[i][j][k] , g[i-1][j][k] ) , g[i][j+1][k] = max ( g[i][j][k] , g[i][j+1][k] );

	for ( int i = 1 ; i <= n ; i ++ ) f[i][i] = g[i][i][i];
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
			for ( int k = l ; k <= r ; k ++ )
				f[l][r] = max ( f[l][r] , f[l][k-1] + f[k+1][r] + g[l][r][k] );
	cout << f[1][n] << endl;
	return 0;
}

P4766 [CERC2014] Outer space invaders

感觉区间 \(dp\) 的题真是很让人迷惑

\(f[l][r]\) 表示处理 \([l,r]\) 时间区间内的所有外星人(即 \(l\le a_i\le b_i\le r\) 的所有外星人) 的最小成本

设区间内距离最远的敌人为 \(r\) 那么我们一定会在 \([l,r]\) 区间内选一个时间点 \(k\) 去杀死这个最远的敌人 (\(a_r\le k\le b_r\))

那么因为 \([l,r]\) 区间内的所有点对可以被分为三类:

  • 全部在 \([l,k-1]\) 中的
  • \(k\) 有交集的
  • 全部在 \([k+1,r]\) 中的

第一种和第三种我们已经处理好了 而所有和 \(k\) 有交集的都可以通过一次消除(代价为 \(r\)) 来解决 所以正确性得到了保证

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+(r-l)/2)
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
#define pii pair<int,int>
#define fi first
#define se second
#define getchar() cin.get()
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , f[N][N];

struct node { int l , r , d; } a[N];

vector<int> lsh;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	int T = read();
	while ( T -- )
	{
		n = read();
		lsh.clear();
		for ( int i = 1 ; i <= n ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].d = read() , lsh.eb(a[i].l) , lsh.eb(a[i].r);
		sort ( lsh.begin() , lsh.end() );
		lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
		for ( int i = 1 ; i <= n ; i ++ ) a[i].l = lower_bound ( lsh.begin() , lsh.end() , a[i].l ) - lsh.begin() + 1 , a[i].r = lower_bound ( lsh.begin() , lsh.end() , a[i].r ) - lsh.begin() + 1;
		int tot = lsh.size();
		for ( int len = 1 ; len <= tot ; len ++ )
			for ( int l = 1 , r = l + len - 1 ; r <= tot ; l ++ , r ++ )
			{
				f[l][r] = inf; int p = 0;
				for ( int i = 1 ; i <= n ; i ++ ) if ( l <= a[i].l && a[i].r <= r && a[i].d > a[p].d ) p = i;
				if ( !p ) { f[l][r] = 0; continue; }
				for ( int k = a[p].l ; k <= a[p].r ; k ++ ) 
					f[l][r] = min ( f[l][r] , f[l][k-1] + f[k+1][r] + a[p].d );
			}
		cout << f[1][tot] << endl;
	}
	return 0;
}

P4342 [IOI1998] Polygon

显然我们可以断环成链

对于转移 我们记录 \(f[l][r]\) 表示区间内答案最大值 \(g[l][r]\) 表示区间内答案最小值 那么我们枚举中间断点 \(k\) 然后枚举四种情况更新即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define ls p<<1
#define rs p<<1|1
#define mid (l+(r-l>>1))
#define pii pair<int,int>
#define lson ls,l,mid
#define rson rs,mid+1,r
#define fi first
#define se second
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int inf = 0x3f3f3f3f;
const int N = 1e3 + 5;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , a[N] , f[N][N] , g[N][N] , ans = -inf;
char op[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) cin >> op[i] , a[i] = read() , op[i+n] = op[i] , a[i+n] = a[i];
	memset ( f , -inf , sizeof f ) , memset ( g , inf , sizeof g );
	for ( int i = 1 ; i <= ( n << 1 ) ; i ++ ) f[i][i] = g[i][i] = a[i];
	for ( int len = 2 ; len <= ( n << 1 ) ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= ( n << 1 ) ; l ++ , r ++ )
			for ( int k = l ; k < r ; k ++ )
			{
				if ( op[k+1] == 't' ) f[l][r] = max ( f[l][r] , f[l][k] + f[k+1][r] ) , g[l][r] = min ( g[l][r] , g[l][k] + f[k+1][r] );
				else 
				{
		ccbbbbbbbbbbbbbbfcvvvvvvvvvvvvvvvvvvvvvvvvbgbbbbbbbbbnhhmmmkhhaaa			f[l][r] = max ( f[l][r] , max ( max ( f[l][k] * f[k+1][r] , g[l][k] * g[k+1][r] ) , max ( f[l][k] * g[k+1][r] , g[l][k] * f[k+1][r] ) ) );
					g[l][r] = min ( g[l][r] , min ( min ( f[l][k] * f[k+1][r] , g[l][k] * g[k+1][r] ) , min ( f[l][k] * g[k+1][r] , g[l][k] * f[k+1][r] ) ) );
				}
			}

	for ( int i = 1 ; i <= n ; i ++ ) ans = max ( ans , f[i][i+n-1] );
	cout << ans << endl;
	for ( int i = 1 ; i <= n ; i ++ ) if ( f[i][i+n-1] == ans ) cout << i << ' ';
	cout << endl;
	return 0;
}

P4805 [CCC2016] 合并饭团

\(f[l][r]\) 表示 \([l,r]\) 区间内的最大饭团 转移非常显然 对于第一种操作直接枚举 \(k\) 即可

对于第二种操作 我们需要枚举两个断点 \(k,p\) 来固定中间的区间

这样转移是 \(O(n^4)\) 的 需要在循环判断条件中加一点三目优化来卡常

对于 \(O(n^3)\) 的正解 我们可以用双指针优化一重循环 因为 \([l,k]\) 一定是比 \([l,k+1]\) 要小(不算无解情况) 那么我们可以用双指针来移动 \(k,t\) 端点来保证时间复杂度

判断转移的合法性时 如果区间为 \(0\) 那么是不合法的

暴力: \(O(n^4)\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 400 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , ans , f[N][N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	memset ( f , -1 , sizeof f );
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) f[i][i] = read();
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
		{
			for ( int k = l ; k < r ; k ++ ) if ( f[l][k] == f[k+1][r] && f[l][k] != -1 ) f[l][r] = max ( f[l][r] , f[l][k] + f[k+1][r] ); 
			for ( int k = l + 1 ; k <= r - 1 ; k ++ )
				for ( int p = k ; p < r ; p ++ ) 
					if ( f[l][k-1] == f[p+1][r] && f[l][k-1] != -1 ) f[l][r] = max ( f[l][r] , f[l][k-1] + f[p+1][r] + ( f[k][p] == -1 ? -inf : f[k][p] ) );
		}
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = i ; j <= n ; j ++ )
			ans = max ( ans , f[i][j] );
	cout << ans << endl;
	return 0;
}

正解:\(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 400 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , ans , f[N][N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) f[i][i] = read();
	for ( int len = 2 ; len <= n ; len ++ )
		for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
		{
			for ( int k = l ; k < r ; k ++ ) if ( f[l][k] == f[k+1][r] && f[l][k] ) f[l][r] = max ( f[l][r] , f[l][k] + f[k+1][r] );
			for ( int k = l , t = r ; k < t - 1 ; )
			{
				if ( f[l][r] ) break;
				if ( !f[l][k] || f[l][k] < f[t][r] ) ++ k;
				else if ( !f[t][r] || f[l][k] > f[t][r] ) -- t;
				else if ( f[l][k] == f[t][r] )
				{
					if ( f[k+1][t-1] ) f[l][r] = f[l][k] + f[k+1][t-1] + f[t][r];
					else ++ k , -- t;
				}
			}
		}
	for ( int i = 1 ; i <= n ; i ++ ) 
		for ( int j = i ; j <= n ; j ++ )
			ans = max ( ans , f[i][j] );
	cout << ans << endl;
	return 0;
}
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define inl inline
#define eb emplace_back
#define mid (l+r>>1)
#define getchar() cin.get()
#define print(x) cout<<#x<<'='<<x<<endl
const int N = 100 + 5;
const int inf = 0x3f3f3f3f;

int read()
{
	int f = 1 , x = 0;
	char ch = getchar();
	while ( !isdigit(ch) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit(ch) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , k , f[N][N] , st[N][N] , merg[N][N];
string s;
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ )
		cin >> s , merg[s[1]-'A'+1][s[2]-'A'+1] |= 1 << ( s[0] - 'A' + 1 );
	k = read();
	while ( k -- )
	{
		memset ( f , inf , sizeof f );
		memset ( st , 0 , sizeof st );
		cin >> s , n = s.size() , s = " " + s;
		for ( int i = 1 ; i <= n ; i ++ ) st[i][i] |= 1 << ( s[i] - 'A' + 1 ) , f[i][i] = ( ( s[i] == 'S' ) ? 1 : inf );
		for ( int len = 2 ; len <= n ; len ++ )
			for ( int l = 1 , r = l + len - 1 ; r <= n ; l ++ , r ++ )
			{
				for ( int k = l ; k < r ; k ++ )
				{
					f[l][r] = min ( f[l][r] , f[l][k] + f[k+1][r] );
					for ( int x = 1 ; x <= 26 ; x ++ )
						for ( int y = 1 ; y <= 26 ; y ++ )
							if ( ( st[l][k] & ( 1 << x ) ) && ( st[k+1][r] & ( 1 << y ) ) )
								st[l][r] |= merg[x][y];
				}
				if ( st[l][r] & ( 1 << ( 'S' - 'A' + 1 ) ) ) f[l][r] = 1;
			}
		// cout << ( !! ( st[3][3] & ( 1 << 'C' - 'A' + 1 ) ) ) << endl;
		if ( f[1][n] == inf ) cout << "NIE" << endl;
		else cout << f[1][n] << endl;
	}
	return 0;
}
posted @ 2023-09-25 15:35  Echo_Long  阅读(5)  评论(0编辑  收藏  举报