区间上的dp状态设计最基本的形式:

\(F[i]\)表示以i结尾的最优值或方案数。

\(F[i][k]\)表示以i结尾附加信息为k的最优值或方案数。

当然可以有多维附加信息。

转移的话往往是枚举上一个断点。

\(F[i]=max \{ F[j]+ w(j+1,i) | j是一个满足转移条件的断点\}\)。

另一个很常见的是:$ f[i][j]$前i个位置分成j段/选出j个的最优值。

这是最简单的一类序列上的dp

bzoj1003

有m个码头和e条航线,每天航线有成本。有连续n天需要从1号码头到m号码头运输货物。每个码头会在某些天数区间内不许经过。每更换一次运输路线,要付出k的成本。

求这n天的最小总成本。

m<=20, n<=100

SOLUTION:

其实就是分成很多段,每一段选同一个运输路线,然后得到一个最优的划分方案,使得成本最小。

f[i]表示前i天的运输最小成本。

\(f[i]=min\{ f[j]+k+w(j+1,i)*(i-j) | j<i \}\)

其中w(x,y)表示最短的在第x天到第y天都能用的路线长度;

处理方法:

首先枚举所有的x,y,然后利用最短路算法(这里dijkstra)算出x~y这些天都可以满足的1到m的最短路径;(计算方法:首先数组$use[i][j] $记录第i个点在第j天是否可以使用,在dijkstra时传入x和y,提前预处理,用数组used[i]记录在第x天到第y天是否可以使用码头i,在更新时如果遇到不能使用的点,直接continue掉即可)

复杂度O(N^2 * m * log(m)) 然后数据范围很小嘛所以可以过√

CODE:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#define pr pair<int,int>
#define mk make_pair using namespace std; inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} const int inf=2147483647;
struct node{
int to,dis,nxt;
}e[500];
int ecnt,head[25];
void add(int from,int to,int dis){
++ecnt;
e[ecnt].to=to;
e[ecnt].dis=dis;
e[ecnt].nxt=head[from];
head[from]=ecnt;
}
int n,m,k,E;
int d;
long long wei[110][110];
bool use[25][110];
void hs(int p,int f,int t){//记录第p个码头在第f天到第t天不能用
for(int i=f;i<=t;i++)
use[p][i]=1;
}
bool vis[25],used[25];
int dis[25];
priority_queue<pr,vector<pr>,greater<pr> > q;
long long dijkstra(int x,int y){
while(!q.empty()) q.pop();
memset(used,0,sizeof(used));
memset(vis,0,sizeof(vis));
q.push(mk(0,1));
for(int i=1;i<=m;i++)
dis[i]=inf;
for(int i=x;i<=y;i++)
for(int j=1;j<=m;j++)
if(use[j][i]) used[j]=1;//预处理x~y天不能用的码头,标记为1;
dis[1]=0; while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u],v,w;i;i=e[i].nxt){
v=e[i].to;w=e[i].dis;
if(used[v]) continue;//遇到不能用的码头,continue;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push(mk(dis[v],v));
}
}
}
return dis[m];
} long long f[110]; void dp(){//计算前n天从1=>m码头的最短花费
//显然最短花费是
for(int i=1;i<=n;i++){
f[i]=(long long)wei[1][i]*i;
for(int j=1;j<i;j++){
f[i]=min(f[i],f[j]+k+wei[j+1][i]*(i-j));
}
}
} int main(){
n=read();m=read();
k=read();E=read();
for(int i=1,u,v,w;i<=E;i++){
u=read();
v=read();
w=read();
add(u,v,w);
add(v,u,w);
}
d=read();
for(int i=1,p,a,b;i<=d;i++){
p=read();
a=read();
b=read();
hs(p,a,b);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
wei[i][j]=dijkstra(i,j);
dp();
cout<<f[n]<<endl;
return 0;
}

bzoj1296 粉刷匠

有n条木板要被粉刷,每条木板分为m个格子,每个格子需要被刷成蓝色或红色。

每次粉刷可以在一条木板上给连续的一段格子刷上相同的颜色。每个格子最多被刷一次。

问若只能刷k次,最多正确粉刷多少格子。

n,m<=50, k<=2500

如果只有一条木板,那么设\(g[i][j]\)表示前i个格子刷j次的最多正确格子

然后枚举一个k,表示前k个格子刷了j-1次,第k+1到第i个格子刷一次的情况,这样依次枚举求出\(g[i][j]\)的最大值

\[g[i][j]=max\{ g[k][j-1]+w(k+1,i) | k<i \}
\]

w(x,y)为第x到第y个格子的最多同色格子数,哪个颜色出现的多刷哪个,直接记一个前缀和即可。???对怎么处理w(x,y)表示疑惑???

大概是这个意思叭:

for(int i=1;i<=m;i++){
if(color[i]=='0') {
Sumr[i]=Sumr[i-1]+1;
Sumb[i]=Sumb[i-1];
}
else {
Sumb[i]=Sumb[i-1]+1;
Sumr[i]=Sumr[i-1];
}
}
w[i][j]=max(Sumb[j]-Sumb[i-1],Sumr[j]-Sumr[i-1]);

有多条木板,设\(f[i][j]\)表示前i个木板刷j次的最大答案。

\(f[i][j]=Max\{ f[i-1][k]+g_i[m][j-k] | k<=j \}\)

也就是需要先处理出每一块木板的g数组;


然后叭,其实这是一个有TLE的代码,但\(o_2\)优化,你值得拥有!

基本上都是上面↑讲到的式子叭,但是有几个要注意的点:

  1. 处理g数组时,枚举k要从0开始枚举;
  2. \(g[i][j][k]\)在k!=0时,初值为1;k=0时初值为0;
#include<bits/stdc++.h>

using namespace std;

inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} int n,m,t;
int g[55][55][2505];
char color[60];
int Sumr[55],Sumb[55];
int f[55][2505]; void hs(int n){
for(int i=1;i<=m;i++)
for(int j=1;j<=t;j++){
g[n][i][j]=1;
for(int k=0;k<i;k++)
g[n][i][j]=max(g[n][i][j],g[n][k][j-1]+max(Sumr[i]-Sumr[k],Sumb[i]-Sumb[k]));
}
} void clear(){
for(int i=0;i<=m;i++) Sumr[i]=Sumb[i]=0;
} int main(){
n=read();m=read();t=read();
for(int i=1;i<=n;i++) {
scanf("%s",color+1);
clear();
for(int j=1;j<=m;j++){
if(color[j]=='1') {
Sumr[j]=Sumr[j-1];
Sumb[j]=Sumb[j-1]+1;
}
else {
Sumr[j]=Sumr[j-1]+1;
Sumb[j]=Sumb[j-1];
}
}
hs(i);
}
for(int i=1;i<=n;i++)
for(int j=0;j<=t;j++)
for(int k=0;k<=j;k++)
f[i][j]=max(f[i][j],f[i-1][k]+g[i][m][j-k]); cout<<f[n][t]<<endl;
return 0;
}

括号序列模型及解法

给定一个长度为n的仅包含左右括号和问号的字符串,将问号变成左括号或右括号使得该括号序列合法,求方案总数。

例如(())与()()都是合法的括号序列。

n<=3000。

然后反正没找到例题叭,所以没有办法验证对错了,只能先写写看;

然鹅并没有写对啊,太难过了

只能先复制一下zhhx的solution了(溜

令\(dp[i][j]\)表示当前到第i个字符,现在还有j个左括号。

那么分三种情况考虑。

  1. 若第i+1个字符是左括号,则能转移到\(dp[i+1][j+1]\)。
  2. 若第i+1个字符是右括号,则能转移到\(dp[i+1][j-1]\)。
  3. 若第i+1个字符是问号,则能转移到\(dp[i+1][j-1]\)与\(dp[i+1][j+1]\)。

最终\(dp[n][0]\)就是方案总数啦。

时间复杂度为\(O(n^2)\)。

首先感谢小蒟蒻皮皮鱼的友情代码(真的自己写枯了,果然还是太弱了

思维上确实不如神仙皮皮鱼

神仙皮皮鱼把这道题出成了.jpg

U86873 小Y的精灵国机房之旅

#include<bits/stdc++.h>

using namespace std;

inline int read() {
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=10010;
const int mod=1000000007;
int n;
char c[mxn];
int f[3][10010]; int main(){
n=read();
scanf("%s",c+1);
f[0][0]=1;
for(int i=1;i<=n;i++) {
for(int j=i;j>=0;j--) {
f[i&1][j]=0;
if(c[i]=='Y') {
if(j>0)
f[i&1][j]=f[(i-1)&1][j-1]%mod;
}
if(c[i]=='H') {
f[i&1][j]=f[(i-1)&1][j+1]%mod;
}
if(c[i]=='C') {
f[i&1][j]=f[(i-1)&1][j+1]%mod;
if(j>0)
f[i&1][j]=(f[i&1][j]+f[(i-1)&1][j-1])%mod;
}
}
}
printf("%d",f[n&1][0]%mod);
return 0;
}

BZOJ3709

在一款电脑游戏中,你需要打败n只怪物(从1到n编号)。为了打败第i只怪物,你需要消耗d[i]点生命值,但怪物死后会掉落血药,使你恢复a[i]点生命值。任何时候你的生命值都不能降到0(或0以下)。

请问是否存在一种打怪顺序,使得你可以打完这n只怪物而不死掉。

N<=10^5

这个题是不是蜜汁眼熟?

没错!它就是lyd之前讲贪心讲到的那个题:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define ll long long using namespace std; inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} int n,x,y,upc,downc;
ll z;
struct node{
int a,d,id;
}up[100005],down[100005]; bool cmp1(node i,node j){
return i.d<j.d;
} bool cmp2(node i,node j){
return i.a>j.a;
} int main(){
n=read();scanf("%lld",&z);
for(int i=1;i<=n;i++){
x=read();y=read();
if(x>y) down[++downc].a=y,down[downc].d=x,down[downc].id=i;
else up[++upc].a=y,up[upc].d=x,up[upc].id=i;
}
sort(up+1,up+upc+1,cmp1);
sort(down+1,down+downc+1,cmp2);
for(int i=1;i<=upc;i++){
z-=up[i].d;
if(z<=0){
printf("NIE");
return 0;
}
z+=up[i].a;
}
for(int i=1;i<=downc;i++){
z-=down[i].d;
if(z<=0) {
printf("NIE");
return 0;
}
z+=down[i].a;
}
printf("TAK\n");
for(int i=1;i<=upc;i++)printf("%d ",up[i].id);
for(int i=1;i<=downc;i++) printf("%d ",down[i].id);
return 0;
}

bzoj4922

给出一些括号序列,要求选择一些括号序列拼接成一个合法的括号序列,使得总长最大。

1<=n<=300,表示括号序列的个数

括号序列的长度len不超过300.

这是一道调了3天的题(我太难了)

首先对于已经配对的括号,我们不必再去考虑,因此可以先将其删掉:

举个例子:

))()(),显然对于子串"()()",我们可以不考虑它。

那么最后消成的,一定是这样的序列:))…)((…((,左端为右括号,右端为左括号

因此我们可以先将这一部分处理掉:

//用x来记录没有消掉的右括号的数量,y记录没有消掉的左括号的数量
/*结构体struct node{
int l,r,len;
//l表示这个序列的左端右括号数
//r表示这个序列的右端左括号数
//len表示这段序列的长度
}a[mx]*/
for(int i=1,len,x,y;i<=n;i++){
scanf("%s",s+1);
x=y=0;
len=strlen(s+1);
sum+=len;//记录整个字符串共有多长
a[i].len=len;
for(int i=1;i<=len;i++) {
if(s[i]=='(') y++;
else y?y--:x++;
}
a[i].l=x;
a[i].r=y;
}

然后回到了一个很熟悉的题目:

按照这道题的贪心思路,我们把左括号看成+1,右括号看成-1,首先肯定是要考虑左边右括号小于右边左括号的,因此将左括号数量>右括号数量的排在前面,右括号数量>左括号数量的排在后面;

然后对于每一部分,我们又应该怎么排列呢?

对于左括号数量>右括号数量的,我们按照右括号数量,右括号越少,排的位置越靠前。

对于左括号数量<右括号数量的,我们按照左括号数量,左括号越多,排的位置越靠前。

bool cmp(node x,node y){
if((x.l<x.r)&&(y.l<y.r)) return x.l<y.l;//如果比较的两个字符串都是左括号数>右括号数,比较右括号数量,右括号少的在前;
if((x.l<x.r)&&(y.l<y.r)) return x.l<x.r;//如果比较的两个字符串中有一个满足左括号数>右括号数,另一个不满足(两个都满足的情况会在上面return掉),那么我们看一下x是否满足右括号数<左括号数,如果满足,显然x在前y在后,反之。
return x.r>y.r;//最后剩下的是左括号数<右括号数的,也就按照左括号数从大到小排序;
} int main(){
……
sort(a+1,a+n+1,cmp);
……
return 0;
}

按照这样排序之后,我们进行dp:

设\(f[i][j]\)表示前i个字符串,左右括号之和为j(左+1右-1)时的长度最长是多少w?

然后考虑转移:

对于\(f[i][j]\)如果不选第i个子串:\(f[i][j]<=f[i-1][j]\)

如果选择第i个子串:\(f[i][j]<=f[i-1][j-a[i].r+a[i].l]+a[i].len\)

因此,\(f[i][j]\)最终结果是在两者中取max:\(f[i][j]=max(f[i-1][j],f[i-1][j-a[i].r+a[i].l]+a[i].len);\)

然后注意考虑\(j-a[i].r+a[i].l>=0\)

\(CODE:\)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll long long using namespace std; inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
} int n,B,A,ans;
char s[310];
struct node{
int l/*left )*/,r/*right (*/,len;
}a[310]; int f[310][90001]; bool cmp(node x,node y){
if((x.l<x.r)&&(y.l<y.r)) return x.l<y.l;
if((x.l<x.r)||(y.l<y.r)) return x.l<x.r;
return x.r>y.r;
} int main(){
n=read();
//x:right brackets
//y:left brackets
int sum=0;
for(int i=1,len,x,y;i<=n;i++){
scanf("%s",s+1);
x=y=0;
len=strlen(s+1);
sum+=len;
a[i].len=len;
for(int i=1;i<=len;i++) {
if(s[i]=='(') y++;
else y?y--:x++;
}
a[i].l=x;
a[i].r=y;
} sort(a+1,a+n+1,cmp);
memset(f,200,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=sum;j++){
f[i][j]=f[i-1][j];
if(i==n&&a[i].r!=0&&j==0) continue;
if(j+a[i].l-a[i].r>=0)
f[i][j]=max(f[i][j],f[i-1][j+a[i].l-a[i].r]+a[i].len);
}
}
printf("%d",f[n][0]);
return 0;
}

区间和序列上的dp的更多相关文章

  1. 『序列 莫队 dp预处理』

    序列 Description 给定长度为n的序列:a1,a2,-,an,记为a[1:n]. 类似地,a[l:r](1≤l≤r≤N)是指序列:al,al+1,-,ar-1,ar.若1≤l≤s≤t≤r≤n ...

  2. UVA - 10131Is Bigger Smarter?(DAG上的DP)

    题目:UVA - 10131Is Bigger Smarter? (DAG) 题目大意:给出一群大象的体重和IQ.要求挑选最多的大象,组成一个序列.严格的体重递增,IQ递减的序列.输出最多的大象数目和 ...

  3. 【2019.7.25 NOIP模拟赛 T3】树(tree)(dfs序列上开线段树)

    没有换根操作 考虑如果没有换根操作,我们该怎么做. 我们可以求出原树的\(dfs\)序列,然后开线段树维护. 对于修改操作,我们可以倍增求\(LCA\),然后在线段树上修改子树内的值. 对于询问操作, ...

  4. bzoj4032/luoguP4112 [HEOI2015]最短不公共子串(后缀自动机+序列自动机上dp)

    bzoj4032/luoguP4112 [HEOI2015]最短不公共子串(后缀自动机+序列自动机上dp) bzoj Luogu 题解时间 给两个小写字母串 $ A $ , $ B $ ,请你计算: ...

  5. ZOJ1232 Adventure of Super Mario spfa上的dp

    很早之前听说有一种dp是在图上的dp,然后是在跑SPFA的时候进行dp,所以特地找了一题关于在SPFA的时候dp的. 题意:1~a是村庄 a+1~a+b是城堡,存在m条无向边.求由a+b->1的 ...

  6. Codeforces 811C Vladik and Memorable Trip (区间异或最大值) (线性DP)

    <题目链接> 题目大意: 给你n个数,现在让你选一些区间出来,对于每个区间中的每一种数,全部都只能出现在这个区间. 每个区间的价值为该区间不同的数的异或值之和,现在问你这n个数最大的价值是 ...

  7. Python: 在序列上执行聚集函数(比如sum() , min() , max() )

    在序列上执行聚集函数(比如sum() , min() , max() ) eg1: >>>nums = [1, 2, 3, 4, 5]>>>s = sum(x * ...

  8. BZOJ 3998 TJOI2015 弦论 后缀自动机+DAG上的dp

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3998 题意概述:对于一个给定长度为N的字符串,求它的第K小子串是什么,T为0则表示不同位置 ...

  9. Contest1874 - noip基础知识五:动态规划(背包、树dp、记忆化、递推、区间、序列dp、dp优化)

    传送门 T1  dp[n][m]=dp[n-1][m-1]+dp[n-m][m] T2  ans=cat(n)*(n!)2  卡特兰数 T3  dp[i][j]=sigma(dp[i-1][j-a[i ...

随机推荐

  1. 计蒜客 2018南京网络赛 I Skr ( 回文树 )

    题目链接 题意 : 给出一个由数字组成的字符串.然后要你找出其所有本质不同的回文子串.然后将这些回文子串转化为整数后相加.问你最后的结果是多少.答案模 1e9+7 分析 : 应该可以算是回文树挺裸的题 ...

  2. 【转】稳定婚姻问题(Stable Marriage Problem)

    转自http://www.cnblogs.com/drizzlecrj/archive/2008/09/12/1290176.html 稳定婚姻是组合数学里面的一个问题. 问题大概是这样:有一个社团里 ...

  3. html密码框value为空,但是总有默认密码(原)

    input输入框加属性:autocomplete="new-password" ,浏览器就不会给他填充默认密码. <input class="form-contro ...

  4. JavaWeb_(设计模式)单例模式

    菜鸟教程 传送门 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该 ...

  5. Unity3D_(游戏)2D简单游戏制作过程:捕获高空掉落保龄球

      游戏介绍:通过鼠标的左右移动,可以控制帽子的移动,当帽子接到下落的保龄球时,会出现火花效果.没有接到保龄球时,保龄球落到草地上,过10S后会自动消失. 实现效果: 素材+Unity3D源代码:传送 ...

  6. 揭秘Android Studio项目目录结构

    I don't know if this is because of the Gradle Build System (I'd wager it is), but I'll tell you what ...

  7. sublime text3 最新 license注册码分享 2018

    —– BEGIN LICENSE —– Die Socialisten GmbH 10 User License EA7E-800613 51311422 E45F49ED 3F0ADE0C E5B8 ...

  8. C#程序模拟登录批量获取各种邮件内容信息

    一般来说,如果现实中你有这样一种需求“假如你是褥羊毛的羊毛党,你某日发现了一个app有一个活动,通过邮箱注册账号激活可以领5元红包,而恰恰你手上又有一批邮箱可用,那么批量获取邮箱中的激活链接去激活则是 ...

  9. C++入门经典-例3.15-使用do-while循环计算1到10的累加

    1:代码如下: // 3.15.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> usin ...

  10. shiro.ini

    # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreeme ...