博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DP合集
阅读量:5288 次
发布时间:2019-06-14

本文共 10131 字,大约阅读时间需要 33 分钟。

区间\(DP\),入门题.环状\(DP\)先考虑线段.

我们用一个区间\([l,r]\)表示石子,意义为该石子是由\([l,r]\)区间的石子合并而来.
显然,最终一定只剩一堆石子\([l,r]\),且\(\exists k\)使得\([l,r]\)先合并为\([l,k]\)\([k+1,r]\)再合并为\([l,r]\).
于是我们可以很自然的想到从左右端点重合的区间(即单个元素)开始向上递推.
由于最大值最小值情况相同,所以只讨论最大值.
于是,设\(f_{i,j}\)表示把区间\([l,r]\)合并起来能得到的最大值.
于是我们枚举断点就行了,不断递推即可.我们发现,转移需要快速求区间和.
所以还需要一个前缀和,记前缀和为\(s_i\).
于是得到转移方程:
\[f_{i,j}=max(f_{i,j},f_{i,k}+f_{k+1,j}+s_j-s_{i-1})\]
\(f\)数组的初值显然为\(0\).
线段考虑完成之后,考虑环状怎么做.
显然,原数组倍长断环成链即可.
\(Code:\)

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#define int long long#define pb push_backusing 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 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#define int long long#define pb push_backusing 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 ;}

这个题并非只是简单的\(\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=max_{j\in [1,i]\&\&v_j<v_i}{f_j}+1\]
我们发现这个状态是很难进行推广的.
于是考虑去改变状态.
\(f_i\)表示长度为\(i\)的最长上升子序列的结尾元素目前可能的最小值.
因为我们当前最长上升子序列的结尾元素肯定是越小越好,这样我们才有更多的"空间"去放别的数字.
考虑这样如何转移:
令当前长度是\(cnt\).
如果有\(f_{cnt}<v_i\),则直接放入当前最长上升子序列的末尾,\(++cnt\).
否则,考虑调整,由于\(v_i\le f_{cnt}\),所以在长度较小的上升子序列中可能存在一个位置可供\(v_i\)使用.
即把某个子序列中不如\(v_i\)的结尾元素替换为\(v_i\).
二分实现即可,显然\(f\)数组具有单调性.

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#define int long long#define pb push_backusing 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\)的时候考虑这几种情况就行了.不过目前我还完全不会修改..只会静态的.
至于这个的例题,有:
原题代码
\(Code:\)

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#define int long long#define pb push_back#define db doubleusing 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 
#include
#include
#define ls ( rt << 1 )#define rs ( rt << 1 | 1 )#define mid ( ( l + r ) >> 1 )#define int long longconst 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 ;}

咕咕咕~

转载于:https://www.cnblogs.com/Equinox-Flower/p/11551847.html

你可能感兴趣的文章
indexof使用
查看>>
Weblogic Cluster环境下apache报错
查看>>
[HAOI2009][BZOJ2431] 逆序对数列
查看>>
对象的引用
查看>>
Visual Studio 2013下JSON可视化工具
查看>>
PHP导出数据到表格的实例
查看>>
back键彻底关闭应用程序
查看>>
hadoop中hive常用的交互式操作
查看>>
dos窗口出现error:could not open ...jvm.cfg解决方法
查看>>
polyfit线性拟合函数
查看>>
swiper插件简介及用法
查看>>
物理引擎入门
查看>>
P1447 [NOI2010]能量采集
查看>>
Linux常用文件介绍
查看>>
10、jstl标签库
查看>>
iOS编码规范参考
查看>>
ios UITableView背景图片设置
查看>>
web第二周作业(编写代码,有关于input 的元素type对于不同的属性的应用实现,类似于注册)...
查看>>
黑马程序员------OC中协议和分类
查看>>
关押罪犯
查看>>