DP合集
区间\(DP\),入门题.环状\(DP\)先考虑线段.
我们用一个区间\([l,r]\)表示石子,意义为该石子是由\([l,r]\)区间的石子合并而来.
显然,最终一定只剩一堆石子\([l,r]\),且\(\exists k\)使得\([l,r]\)先合并为\([l,k]\)和\([k+1,r]\)再合并为\([l,r]\).
于是我们可以很自然的想到从左右端点重合的区间(即单个元素)开始向上递推.
由于最大值最小值情况相同,所以只讨论最大值.
于是,设\(f_{i,j}\)表示把区间\([i,j]\)合并起来能得到的最大值.
于是我们枚举断点就行了,不断递推即可.我们发现,转移需要快速求区间和.
所以还需要一个前缀和,记前缀和为\(s_i\).
于是得到转移方程:
\(f\)数组的初值显然为\(0\).
线段考虑完成之后,考虑环状怎么做.
显然,原数组倍长断环成链即可.
\(Code:\)
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int M = 500 ;
const int inf = 0x7f7f7f7f ;
int n , v[M] , f[M][M] , g[M][M] , m , sum[M] ;
int maxn , minn = inf ;
signed main (int argc , char * argv[]) {
n = rint () ; m = n << 1 ;
rep ( i , 1 , n ) { v[i] = rint () ; v[i+n] = v[i] ; }
rep ( i , 1 , m ) sum[i] = sum[i-1] + v[i] ;
per ( i , m - 1 , 1 ) rep ( j , i + 1 , i + n - 1 ) {
g[i][j] = inf ;
rep ( k , i , j - 1 ) {
f[i][j] = max ( f[i][j] , f[i][k] + f[k+1][j] + sum[j] - sum[i-1] ) ;
g[i][j] = min ( g[i][j] , g[i][k] + g[k+1][j] + sum[j] - sum[i-1] ) ;
}
}
rep ( i , 1 , n ) { maxn = max ( maxn , f[i][i+n-1] ) ; minn = min ( minn , g[i][i+n-1] ) ; }
printf ("%lld\n%lld\n" , minn , maxn ) ;
system ("pause") ; return 0 ;
}
也是看起来非常不可做的一个题.
仔细思考,发现了一个很\(coooooooooool\)的事情:
他是不是让我求最小独立集覆盖...
一个独立集覆盖是指把图划分成若干个子集,每个子集里的点两两互不连通.
然后你\(2^n\)枚举子集,记录是不是一个独立集,然后在独立集上\(DP\).
你就令\(f_S\)表示点集为\(S\)时的最小独立集覆盖.
显然,每次可以从它一个是独立集的子集转移过来,取一个\(min\)就完了(最长路最短).
最后的答案是\(f_{U}-1\),其中\(U\)是全集.为什么减一啊?
因为最长路是边数啊...
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 20 ;
const int MAXS = 1 << 20 ;
int n , m , ans , s[N] ;
int g[MAXS] , f[MAXS] , cnt ;
bool e[N][N] ;
inline bool judge () {
rep ( i , 1 , cnt ) rep ( j , 1 , cnt )
if ( e[s[i]][s[j]] || e[s[j]][s[i]] ) return false ;
return true ;
}
signed main (int argc , char * argv[]) {
n = rint () ; m = rint () ;
rep ( i , 1 , m ) {
int u = rint () , v = rint () ;
e[u][v] = e[v][u] = true ;
}
int maxsize = 1 << n ;
for (int i = 0 ; i < maxsize ; ++ i) {
cnt = 0 ;
for (int j = 0 ; j < n ; ++ j)
if ( i & ( 1 << j ) )
s[++cnt] = j + 1 ;
g[i] = judge () ;
}
MEM ( f , 0x7f ) ; f[0] = 0 ;
for (int i = 0 ; i < maxsize ; ++ i) {
for (int j = i ; j ; j = ( j - 1 ) & i )
if ( ! g[j] ) continue ;
else f[i] = min ( f[i] , f[i^j] + 1 ) ;
}
printf ("%lld\n" , f[maxsize-1] - 1 ) ;
return 0 ;
}
LuoGuP1439
这个题并非只是简单的\(\Theta(n^2)\)的\(LCS\).
我们发现直接\(\Theta(n^2)\)的\(LCS\)显然过不去高达\(10^5\)的数据.
但这题给了个奇妙的性质,它给的是两个排列.
也就是说,两个排列中的数字是相同的,唯一不同的就是数字的位置.
如果我们把第一个排列作为规定的大小顺序,那么我们发现,答案就是第二个排列在这个大小顺序下的\(LIS\).
为什么呢?因为如果在第二个串中一个子序列是上升的,那么它在原序列中也一定是这样排列的.因为我们是按照第一个排列的顺序规定的大小.
所以就只需要映射完跑一遍\(\Theta(n\times log_2n)\)的\(LIS\)即可.
正好说一下\(\Theta(n\times log_2n)\)的\(LIS\)叭.
先来\(\Theta(n^2)\)叭.
令\(f_i\)表示以\(i\)结尾的最长上升子序列的长度.
转移显然.
我们发现这个状态是很难进行推广的.
于是考虑去改变状态.
令\(f_i\)表示长度为\(i\)的最长上升子序列的结尾元素目前可能的最小值.
因为我们当前最长上升子序列的结尾元素肯定是越小越好,这样我们才有更多的"空间"去放别的数字.
考虑这样如何转移:
令当前长度是\(cnt\).
如果有\(f_{cnt}<v_i\),则直接放入当前最长上升子序列的末尾,\(++cnt\).
否则,考虑调整,由于\(v_i\le f_{cnt}\),所以在长度较小的上升子序列中可能存在一个位置可供\(v_i\)使用.
即把某个子序列中不如\(v_i\)的结尾元素替换为\(v_i\).
二分实现即可,显然\(f\)数组具有单调性.
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 1e5 + 100 ;
int n , v[N] , rk[N] , f[N] , cnt ;
signed main (int argc , char * argv[]) {
n = rint () ; rep ( i , 1 , n ) rk[rint()] = i ;
rep ( i , 1 , n ) v[i] = rint () ; f[0] = 0 ;
rep ( i , 1 , n ) {
if ( rk[v[i]] > f[cnt] ) f[++cnt] = rk[v[i]] ;
else {
int pos = std::lower_bound ( f + 1 , f + cnt + 1 , rk[v[i]] ) - f ;
f[pos] = rk[v[i]] ;
}
}
printf ("%lld\n" , cnt ) ;
system ("pause") ; return 0 ;
}
最大子段和
水题.不知道为什么要写一遍,可能是太闲了叭(闲可不是好现象).
我直接上方程了:
令\(f_i\)表示\(1\)到\(i\)中的最大子段和是多少.
\(f_i=max(f_{i-1}+v[i],v[i])\)
完了.
其实这个还可以扩展至区间查询.很不巧,我会.是用线段树去维护.
虽然这不具有直接的区间可合并性,但我们可以考虑最大子段和在合并的时候的构成.
显然,构成有以下几种:
左区间的前缀,左区间的后缀+右区间的前缀,右区间的后缀,整个左区间+右区间的前缀,整个右区间+左区间的后缀,整个区间,左区间中部,右区间中部.
对,只需要\(pushup\)的时候考虑这几种情况就行了.
至于这个的例题,有:静态区间查询最大子段和
\(Updated:\)
单点修改的直接修改到叶子即可.
区间修改不会.
原题代码
\(Code:\)
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
#define db double
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 2e5 + 100 ;
int n , v[N] , f[N] ;
signed main (int argc , char * argv[]) {
n = rint () ;
rep ( i , 1 , n ) v[i] = rint () ;
f[0] = 0 ; int ans = - 0x7f7f7f7f ;
rep ( i , 1 , n ) f[i] = max ( f[i-1] + v[i] , v[i] ) ;
rep ( i , 1 , n ) ans = max ( ans , f[i] ) ;
printf ("%lld\n" , ans ) ;
system ("pause") ; return 0 ;
}
加强版代码:
\(Code:\)
#include <iostream>
#include <cstdlib>
#include <cstdio>
#define ls ( rt << 1 )
#define rs ( rt << 1 | 1 )
#define mid ( ( l + r ) >> 1 )
#define int long long
const int N = 1e5 + 5 ;
template < class T >
inline T max ( T __A , T __B ) { return __A > __B ? __A : __B ; }
struct seg {
int left , right , data ;
int ldata , rdata , mdata ;
} t[(N<<2)] ;
int n , m , v[N] ;
inline void pushup (int rt) {
t[rt].data = t[ls].data + t[rs].data ;
t[rt].ldata = max ( t[ls].ldata , t[ls].data + t[rs].ldata ) ;
t[rt].rdata = max ( t[rs].rdata , t[rs].data + t[ls].rdata ) ;
t[rt].mdata = max ( t[ls].ldata , max ( t[rs].rdata , t[ls].rdata + t[rs].ldata ) ) ;
t[rt].mdata = max ( t[rt].mdata , max ( t[ls].mdata , t[rs].mdata ) ) ;
return ;
}
inline void prim (int rt , int val) { t[rt].data = t[rt].ldata = t[rt].rdata = t[rt].mdata = val ; return ; }
inline void build (int rt , int l , int r) {
t[rt].left = l ; t[rt].right = r ;
if ( l == r ) { prim ( rt , v[l] ) ; return ; }
build ( ls , l , mid ) ; build ( rs , mid + 1 , r ) ;
pushup ( rt ) ; return ;
}
inline seg query (int rt , int ll , int rr) {
int l = t[rt].left , r = t[rt].right ;
if ( ll <= l && r <= rr ) return t[rt] ;
if ( rr <= mid ) return query ( ls , ll , rr ) ;
else if ( ll > mid ) return query ( rs , ll , rr ) ;
else {
seg lans = query ( ls , ll , rr ) , rans = query ( rs , ll , rr ) ;
seg ans ; ans.data = lans.data + rans.data ;
ans.ldata = max ( lans.ldata , lans.data + rans.ldata ) ;
ans.rdata = max ( rans.rdata , rans.data + lans.rdata ) ;
ans.mdata = max ( lans.ldata , max ( rans.rdata , lans.rdata + rans.ldata ) ) ;
ans.mdata = max ( ans.mdata , max ( lans.mdata , rans.mdata ) ) ;
return ans ;
}
}
signed main () {
scanf ("%lld" , & n ) ;
for (int i = 1 ; i <= n ; ++ i) scanf ("%lld" , & v[i] ) ;
scanf ("%lld" , & m ) ; build ( 1 , 1 , n ) ;
while ( m -- ) {
register int u , v ;
scanf ("%lld%lld" , & u , & v ) ;
printf ("%lld\n" , query ( 1 , u , v ).mdata ) ;
}
return 0 ;
}
这题其实就是问把一段序列划分成若干个子段并限制子段大小的方案数.
首先,要把子段的大小集合搞出来.这其实就是求两个不可重集合的交.
可以直接用桶,我用了 \(set\) , 要稍微麻烦一些.
去重之后,就可以直接 \(Dp\) 了,令 \(f_n\) 表示长度为 \(n\) 的序列的方案数.
那么转移显然有:
其中 , \(v_i\) 是子段大小集合, \(m\) 是子段大小的个数 , \([x]\) 当且仅当 \(x\) 表达式为真时为 \(1\) , 否则为 \(0\) .
这样的复杂度是 \(\Theta(n\times max\{v_i\})\) 的,足以通过 \(60\%\) 的数据.
那么对于 \(100\%\) 的数据该怎么去做呢?
我们发现 \(n\) 的范围高达 \(10^{18}\) , 能做到这样复杂度且被我们熟知的算法只有 \(\Theta(size^3\times log_2{n})\) 的矩乘.
并且我们发现,这个 \(DP\) 实际上就是至多有 \(100\) 个相关状态的递推.
所以完全可以尝试构造转移矩阵.
显然,这个转移矩阵应该是 \(100\times 100\) 的.
那么我们先确定初始矩阵是什么样子 \(:\)
它非常的正常,除了有点长.
目标矩阵呢?
转移矩阵呢?
\(100\times 100\) 的,因为你要保留所有状态,他们都可能是有用的.
他长什么样呢?
第一列中显然所有的 \(v_i\) 对应的位置都是 \(1\) , 其他列呢?
显然都可以从上一个矩阵直接抠下来,所以只有 \((i,i+1)\) 是 \(1\) .
然后转移就完了.
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
#define db double
#define ull unsigned long long
#define lowbit(x) ( x & ( - x ) )
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 1e6 + 100 ;
const int mod = 1e9 + 7 ;
set < int > rab , fis , tmp ; int maxv = - 1 ;
int f[N] , n , pr , nf , v[N] , cnt ;
struct Matrix {
int e[105][105] , line , row ;
inline void clear () { MEM ( e , 0 ) ; line = row = 0 ; return ; }
inline void init () { rep ( i , 1 , line ) e[i][i] = 1 ; }
friend Matrix operator * (Matrix a , Matrix b) {
Matrix res ; res.clear () ; res.line = a.line ; res.row = b.row ;
rep ( k , 1 , a.row ) rep ( i , 1 , a.line ) rep ( j , 1 , b.row )
res.e[i][j] = ( res.e[i][j] + a.e[i][k] * b.e[k][j] % mod ) % mod ;
return res ;
}
friend Matrix operator ^ (Matrix a , int p) {
Matrix res ; res.clear () ; res.line = res.row = a.line ; res.init () ;
while ( p ) {
if ( p & 1 ) res = res * a ;
a = a * a ; p >>= 1 ;
}
return res ;
}
} mat , ans ;
signed main (int argc , char * argv[]) {
n = rint () ;
pr = rint () ; rep ( i , 1 , pr ) rab.insert ( rint () ) ;
nf = rint () ; rep ( i , 1 , nf ) fis.insert ( rint () ) ;
for (auto it = rab.begin () ; it != rab.end () ; ++ it) {
int reg = *it ;
if ( fis.find ( reg ) == fis.end () ) continue ;
tmp.insert ( reg ) ;
}
for (auto it = tmp.begin () ; it != tmp.end () ; ++ it) v[++cnt] = *it ;
rep ( i , 1 , cnt ) f[v[i]] = 1 ;
rep ( i , 1 , 100 ) rep ( j , 1 , cnt ) {
if ( i - v[j] < 0 ) continue ;
f[i] = ( f[i] + f[i-v[j]] ) % mod ;
}
if ( n <= 100 ) return printf ("%lld\n" , f[n] ) , 0 ;
mat.clear () ; ans.clear () ;
rep ( i , 1 , cnt ) mat.e[v[i]][1] = 1 ;
rep ( i , 1 , 100 ) mat.e[i][i+1] = 1 ;
mat.line = mat.row = 100 ; ans.line = 1 ; ans.row = 100 ;
rep ( i , 1 , 100 ) ans.e[1][i] = f[100-i] ;
mat = mat ^ n ; ans = ans * mat ;
printf ("%lld\n" , ans.e[1][100] % mod ) ;
return 0 ;
}
简化题意 \(:\)
给定一个序列,求最长的波浪子序列.
单峰的意思是存在一个 \(v_i\) 使得 \(v_i > v_{i-1},v_i > v_{i+1}\) 且 \(1\) 到 \({i-1}\) 单调,\(i+1\) 到 \(n\) 也单调(单调性相反).
令 \(f_{i,0/1}\) 表示前 \(i\) 个数字中,形状是 \(\lor\) 或者 \(\land\) 的最长长度.
每次比较当前元素和上一个元素的大小,考虑继承即可.
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define per(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define pii pair < int , int >
#define one first
#define two second
#define rint read<int>
#define int long long
#define pb push_back
#define db double
#define ull unsigned long long
#define lowbit(x) ( x & ( - x ) )
using std::queue ;
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 1e5 + 100 ;
int n , v[N] ;
int f[N][2] ;
signed main (int argc , char * argv[]) {
n = rint () ; rep ( i , 1 , n ) v[i] = rint () ;
f[1][0] = f[1][1] = 1 ;
rep ( i , 2 , n ) {
if ( v[i] > v[i-1] ) f[i][0] = max ( f[i-1][0] , f[i-1][1] + 1ll ) , f[i][1] = f[i-1][1] ;
else if ( v[i] < v[i-1] ) f[i][1] = max ( f[i-1][1] , f[i-1][0] + 1ll ) , f[i][0] = f[i-1][0] ;
else f[i][1] = f[i-1][1] , f[i][0] = f[i-1][0] ;
}
printf ("%lld\n" , max ( f[n][0] , f[n][1] ) ) ;
#ifndef ONLINE_JUDGE
system ("pause") ;
#endif
return 0 ;
}