区间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;
}