区间dp专题练习

题意

1.Equal Sum Partitions

? 这嘛东西,\(n^2\)自己写去

\[\
\]

\[\
\]

2.You Are the One

感觉自己智力被吊打

\(dp[i][j]\)表示 , 对于当前的一个空栈 , \(i\)到\(j\)这一段都出栈的最小花费

显然是长得一副区间(诡)dp(异)的样子 , 如何转移呢?(建议自己想想吧)

对于一个\(dp[i][j]\),因为这个\(i\)必须是最先入栈的 , 所以我们可以枚举它的出栈时间\(k\) , 那么总贡献就是

\[dp[i+1][k]+(k-i)*a[i]+sum[k+1..j] \cdot (k-i+1)
\]

即先出栈的\(i+1...k\)的贡献和+i自己的贡献+\(k+1..j\)这一段的贡献和它们被推迟\(k-i+1\)位产生的贡献

rep(i,1,n) rep(j,i+1,n) dp[i][j]=1e9;
rep(i,1,n) dp[i][i]=a[i];
drep(i,n,1)
rep(j,i,n)
rep(k,i,j)
dp[i][j]=min(dp[i][j],dp[i+1][k]+dp[k+1][j]+(k-i)*a[i]+(k-i+1)*(s[j]-s[k]));

\[\
\]

\[\
\]

3.Groups

where is my head?

每个人的决策其实就是把一群人分成一组,所以可以直接一个个区间接上去

细节:满足条件的人大于\(R-L+1\)时要取\(min\)

区间dp?

rep(i,1,n) a[i]=rd(),b[i]=rd();
rep(i,0,n) rep(j,0,n) c[i][j]=0;
rep(i,0,n) dp[i]=0;
rep(i,1,n) c[a[i]][n-b[i]]++;
rep(i,0,n-1) rep(j,i+1,n) dp[j]=max(dp[j],dp[i]+min(j-i,c[i][j]));
printf("%d\n",dp[n]);

4.Sky Soldiers

又是假区间dp

const int N=1010;
const double eps=1e-6; int n,m;
map <int,double> M;
typedef map<int,double> ::iterator iter;
int p[N];
double x[N];
int cnt;
double dp[N][51];//前i个,放j个站
double sum[N][N]; int main(){
while(~scanf("%d%d",&n,&m)&&n&&m) {
M.clear();
rep(i,1,n) {
rep(j,1,rd()) {
int p=rd();
double x;scanf("%lf",&x);
M[p]+=x;
}
}
cnt=0;
for(iter it=M.begin();it!=M.end();++it) {
p[++cnt]=it->first;
x[cnt]=it->second;
}//暴力离散
double ans=1e11;
rep(i,0,cnt) rep(j,0,m) dp[i][j]=1e11;
rep(i,1,cnt) rep(j,1,cnt) sum[i][j]=sum[i][j-1]+x[j]*abs(p[i]-p[j]);//预处理便于算贡献
rep(i,1,cnt) dp[i][1]=sum[i][i];
rep(i,1,cnt) {
rep(j,1,m-1) if(dp[i][j]<1e10) {
rep(k,i+1,cnt) {
int mid=(p[i]+p[k])>>1;
mid=lower_bound(p+1,p+cnt+1,mid)-p;
double s=min(sum[i][mid]-sum[i][i]+sum[k][k]-sum[k][mid],sum[i][mid-1]-sum[i][i]+sum[k][k]-sum[k][mid-1]);//贡献
dp[k][j+1]=min(dp[k][j+1],dp[i][j]+s);
}
}
}
rep(i,1,cnt) ans=min(ans,dp[i][m]+sum[i][cnt]-sum[i][i]);
printf("%.2lf\n",ans);
}
}

\[\
\]

\[\
\]

5.Play Game

假区间dp again

\(dp[l1][r1][l2][r2]\)表示两个序列分别还剩下\(l1,r1\),\(l2,r2\)的答案 , 很基础但是有细节

int n,m;
int a[N],b[N];
int dp[N][N][N][N];
int sum1[N],sum2[N]; int main(){
rep(kase,1,rd()) {
n=rd();
rep(i,1,n) sum1[i]=a[i]=rd(),sum1[i]+=sum1[i-1];
rep(i,1,n) sum2[i]=b[i]=rd(),sum2[i]+=sum2[i-1];
memset(dp,0,sizeof dp);
drep(l1,n+1,1) rep(r1,l1-1,n) drep(l2,n+1,1) rep(r2,l2-1,n) {
if(l1>r1&&l2>r2) continue;
int s=1e9;
if(l1<=r1) s=min(s,min(dp[l1+1][r1][l2][r2],dp[l1][r1-1][l2][r2]));
if(l2<=r2) s=min(s,min(dp[l1][r1][l2+1][r2],dp[l1][r1][l2][r2-1]));
dp[l1][r1][l2][r2]=sum1[r1]-sum1[l1-1]+sum2[r2]-sum2[l2-1]-s;
}
printf("%d\n",dp[1][n][1][n]);
}
}

\[\
\]

\[\
\]

6.Two Rabbits

这个鬼题是真的惊了

首先肯定要接两倍数组断成链

然后我们分析题目性质,对于任意路径,互为重合逆子序列时最优

即跳跃路径为原序列的一个回文子序列时一定更优

若两只兔子的路径错开,那么一定可以向两边继续延伸

细节:一个点时回文长度为1,当跳跃总长小于n时答案要加一

int n,m;
int a[N];
int dp[N][N]; int main(){
srand(618);
while(~scanf("%d",&n)&&n){
rep(i,1,n) a[i]=a[i+n]=rd();
int ans=0;
memset(dp,0,sizeof dp);
rep(l,1,n) drep(i,n*2-l+1,1) {
int j=i+l-1;
if(a[i]==a[j]) dp[i][j]=dp[i+1][j-1]+2-(i==j);
else dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
ans=max(ans,dp[i][j]+(l<n));
}
printf("%d\n",ans);
}
}

\[\
\]

\[\
\]

7.Dire Wolf

这个题长得还是比较区间dp的

表示自己的转移错得离谱都过掉了。。。

正确的转移方法和T2有点类似,即枚举最后一个杀死的狼,加上前后直接转移

int n,m;
int a[N],b[N];
int dp[N][N]; int main(){
rep(kase,1,rd()){
rep(i,1,n=rd()) a[i]=rd();
rep(i,1,n) b[i]=rd();b[n+1]=a[n+1]=0;
//memset(dp,10,sizeof dp);
drep(i,n,1) rep(j,i,n) {
dp[i][j]=1e9;
if(i==j) dp[i][j]=a[i]+b[i-1]+b[i+1];
else rep(k,i,j) dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
//第k只狼最后杀死,贡献分开算
}
printf("Case #%d: %d\n",kase,dp[1][n]);
}
}

\[\
\]

\[\
\]

8.Dylans loves sequence

这个题令我不想说话


int n,m;
int a[N],b[N],cnt;
int s[N],ans[N][N];
void Add(int p,int x){
while(p<=n) s[p]+=x,p+=p&-p;
}
int Que(int p){
int res=0;
while(p) res+=s[p],p-=p&-p;
return res;
} int main(){
n=rd(),m=rd();
rep(i,1,n) a[i]=b[i]=rd();
sort(b+1,b+n+1),cnt=unique(b+1,b+n+1)-b-1;
rep(i,1,n) a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
rep(l,1,n) {
memset(s,0,sizeof s);
rep(r,l,n) {
ans[l][r]=ans[l][r-1]+r-l-Que(a[r]);
Add(a[r],1);
}
}
rep(i,1,m) {
int x=rd(),y=rd();
printf("%d\n",ans[x][y]);
}
}

9.Expression

首先要理解题意

所以符号是任意安排顺序,每个符号相当于合并两个数

所以总方案数应该是\((n-1)!\)

转移是也要注意,应该乘上左右两个区间符号交叉出现的价值

将区间\([i,k];[k+1,j]\)合并至\([i,j]\)时要乘上的贡献是

\[\frac{(j-i-1)!}{(k-i)!*(j-k-1)!}
\]

(因为固定了第\(k\)个符号最后,所以是\(j-i-1\))

int n,m;
int a[N],opt[N];
ll g[N][N];
ll fac[N]={1},Inv[N]={1,1}; int main(){
rep(i,1,N-1) fac[i]=fac[i-1]*i%P;
rep(i,2,N-1) Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
rep(i,1,N-1) Inv[i]=Inv[i]*Inv[i-1]%P;
while(~scanf("%d",&n)) {
rep(i,1,n) a[i]=rd();
rep(i,1,n-1) {
char ch;
do ch=getchar();
while(ch!='+'&&ch!='-'&&ch!='*');
if(ch=='+') opt[i]=1;
else if(ch=='-') opt[i]=2;
else opt[i]=3;
}
memset(g,0,sizeof g);
drep(i,n,1) rep(j,i,n) {
if(i==j) g[i][i]=a[i];//边界条件
rep(k,i,j-1) {
ll t=Inv[k-i]%P*Inv[j-k-1]%P*fac[j-i-1]%P;
if(opt[k]==1) {
g[i][j]=(g[i][j]+(fac[k-i]*g[k+1][j]%P+fac[j-k-1]*g[i][k]%P)*t)%P;
} else if(opt[k]==2) {
g[i][j]=(g[i][j]+(-1ll*fac[k-i]*g[k+1][j]%P+1ll*fac[j-k-1]*g[i][k]%P)*t)%P;
} else {
g[i][j]=(g[i][j]+g[i][k]*g[k+1][j]%P*t)%P;
}
}
}
printf("%lld\n",(g[1][n]%P+P)%P);//可能有负数
}
}

\[\
\]

\[\
\]

10.QSC and Master

众所周知我是一个擅长乱搞的人,所以这个奇怪的题我也是乱搞搞过去的

它长得并不是一个标准的区间dp

两种决策:

直接从左右端点向两边扩展 ;

将这段区间切开

注意由于求得是整个序列的答案所以最后还要合并一下

int n,m;
int a[N],b[N]; ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
ll dp[N][N]; int main(){
rep(kase,1,rd()){
n=rd();
rep(i,1,n) a[i]=rd();
rep(i,1,n) b[i]=rd();
memset(dp,-63,sizeof dp);
drep(i,n,1) rep(j,i+1,n) {
if(j-i==1) {
if(gcd(a[i],a[j])!=1) dp[i][j]=b[i]+b[j];
continue;
}
if(gcd(a[i],a[j])!=1) dp[i][j]=max(dp[i][j],dp[i+1][j-1]+b[i]+b[j]);//决策一
rep(k,i,j) dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);//决策二
}
ll ans=0;
rep(i,1,n) {
dp[1][i]=max(dp[1][i],0ll);
rep(j,1,i) dp[1][i]=max(dp[1][i],max(0ll,dp[1][j])+max(0ll,dp[j+1][i]));
}//合并答案
rep(i,1,n) rep(j,i,n) ans=max(ans,dp[i][j]);
printf("%lld\n",ans);
}
}

\[\
\]

\[\
\]

11.Kirinriki

跑错的尺取

const int N=5100,P=1e9+7;

int n,m;
char s[N];
int dp[N]; int main(){
rep(kase,1,rd()){
m=rd();
scanf("%s",s+1);n=strlen(s+1);
int ans=0;
rep(i,3,n*2-1) {
reg int cnt=0,p=1;
drep(j,i/2-(!(i&1)),1) {
if(i-j>n) break;
cnt++;
dp[cnt]=dp[cnt-1]+abs(s[j]-s[i-j]);
while(dp[cnt]-dp[p-1]>m) p++;
ans=max(ans,cnt-p+1);
}
}
printf("%d\n",ans);
}
}

\[\
\]

\[\
\]

12.Zuma

这个题没有素质!它卡常!

\(dp[i][j][0...4]\)表示

0:消完了

1:还剩1个0

2:还剩2个0

3:还剩1个1

4:还剩2个1

转移极其繁琐

卡常技巧:相邻相同的可以压成一个块

(希望我没有想错吧)

const int N=410;
#define chk(a,b) (a>b&&(a=b))//卡常
int n,m;
char s[N];
int a[N],b[N],c[N],dp[N][N][5];
//0 --> 0
//1 --> one 0
//2 --> two 0
//3 --> one 1
//4 --> two 1
int main(){
int T; scanf("%d",&T);
rep(kase,1,T){
scanf("%s",s+1);
n=strlen(s+1);
rep(i,1,n) a[i]=s[i]^'0';
int _n=0;
rep(i,1,n) if(i==1||a[i]!=a[i-1]) {
_n++;
b[_n]=1; c[_n]=a[i];
} else b[_n]++,b[_n]=b[_n];//块合并
n=_n;
rep(i,1,n) rep(j,1,n) rep(o,0,4) dp[i][j][o]=1e9;
rep(i,1,n) {
dp[i][i][c[i]*2+b[i]]=0;
dp[i][i][0]=3-b[i];
}//边界条件初始化
drep(i,n,1) rep(j,i+1,n) {
rep(k,i,j-1){
reg int *x=dp[i][k],*y=dp[k+1][j];//指针卡常
chk(dp[i][j][0],x[0]+y[0]);
chk(dp[i][j][0],x[1]+y[2]);
chk(dp[i][j][0],x[2]+y[2]);
chk(dp[i][j][0],x[2]+y[1]);
chk(dp[i][j][0],x[3]+y[4]);
chk(dp[i][j][0],x[4]+y[3]);
chk(dp[i][j][0],x[4]+y[4]); chk(dp[i][j][1],x[0]+y[1]);
chk(dp[i][j][1],x[1]+y[0]);
chk(dp[i][j][2],x[1]+y[1]);
chk(dp[i][j][2],x[0]+y[2]);
chk(dp[i][j][2],x[2]+y[0]); chk(dp[i][j][3],x[0]+y[3]);
chk(dp[i][j][3],x[3]+y[0]);
chk(dp[i][j][4],x[3]+y[3]);
chk(dp[i][j][4],x[0]+y[4]);
chk(dp[i][j][4],x[4]+y[0]);
}
chk(dp[i][j][0],dp[i][j][1]+2);
chk(dp[i][j][0],dp[i][j][2]+1);
chk(dp[i][j][0],dp[i][j][3]+2);
chk(dp[i][j][0],dp[i][j][4]+1);
}
printf("Case #%d: %d\n",kase,dp[1][n][0]);
}
}

(题解写到一半突然dalao来质问我,发现这种做法好像是错的。。。,但还不清楚是不是)

如果发现有错误请写评论

区间dp专题练习的更多相关文章

  1. 区间dp专题

    HDU4283You Are the One区间dp, 记忆话搜索运行时间:   #include <iostream> #include <cstdio> #include ...

  2. kuangbin专题十二 POJ3186 Treats for the Cows (区间dp)

    Treats for the Cows Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7949   Accepted: 42 ...

  3. 「kuangbin带你飞」专题二十二 区间DP

    layout: post title: 「kuangbin带你飞」专题二十二 区间DP author: "luowentaoaa" catalog: true tags: - ku ...

  4. [kuangbin带你飞]专题二十二 区间DP

            ID Origin Title   17 / 60 Problem A ZOJ 3537 Cake   54 / 105 Problem B LightOJ 1422 Hallowee ...

  5. 专题训练之区间DP

    例题:以下例题部分的内容来自https://blog.csdn.net/my_sunshine26/article/details/77141398 一.石子合并问题 1.(NYOJ737)http: ...

  6. UESTC 2015dp专题 A 男神的礼物 区间dp

    男神的礼物 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/65 Descri ...

  7. 【专题】区间dp

    1.[nyoj737]石子合并 传送门:点击打开链接 描述    有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这 ...

  8. P1040 加分二叉树 区间dp

    题目描述 设一个nn个节点的二叉树tree的中序遍历为(1,2,3,…,n1,2,3,…,n),其中数字1,2,3,…,n1,2,3,…,n为节点编号.每个节点都有一个分数(均为正整数),记第ii个节 ...

  9. dp专题训练

    ****************************************************************************************** 动态规划 专题训练 ...

随机推荐

  1. 通过Nginx为网站配置二级域名

    目录 配置域名解析 配置Nginx 重启Nginx 补充 需求:服务器上面运行多个项目:实现每个二级域名访问对应项目: 服务器:阿里云服务器:域名:阿里云注册: 配置域名解析 即配置DNS解析.一定要 ...

  2. 基于OpenGL三维软件开发

    实验原理: OpenGL在MFC下编程原理---- Windows操作系统对OpenGL的支持 在Windows下用GDI作图必须通过设备上下文(DeviceContext简写DC)调用相应的函数:用 ...

  3. 从 html 元素继承 box-sizing

    在大多数情况下我们在设置元素的 border 和 padding 并不希望改变元素的 width,height值,这个时候我们就可以为该元素设置 box-sizing:border-box;. 我不希 ...

  4. sweetalert 弹框简单使用

    sweetalert网站 简单使用教程 拷贝文件 放到项目中 使用 页面效果 修改代码应用到事件中 成功删除演示(后台数据也会删除) 作 者:郭楷丰 出 处:https://www.cnblogs.c ...

  5. js如何保留两位小数,并进行四舍五入

    保留两位小数,并进行四舍五入使用js函数 toFixed() 函数传递一个参数(Number) Number就为需要保留小数的位数 具体实现代码 <script language="j ...

  6. javascript实现网页倒计时效果

    一.HTML代码如下: <div class="timer" id="timer"> <span style="color: bla ...

  7. linux rwx 权限说明

    Linux的文件和目录的权限,有RWX三种. r(Read,读取):对文件而言,具有读取文件内容的权限:对目录来说,具有浏览目录的权限. w(Write,写入):对文件而言,具有新增,修改,删除文件内 ...

  8. Android 8.0编译过程

    Android编译系统中的Android.bp.Blueprint与Soonghttp://note.qidong.name/2017/08/android-blueprint/ 工具链关系 Andr ...

  9. Webpack如何配置sourceMap

    前言:在写这篇文章之前,我必须要吐槽一下webpack了.特别喜欢更新版本,更新就算了,文档还跟不上.文档真的让人迷惑了,大爷的. 背景:由于我正在写sourceMap反向定位源码的功能,所以最近需要 ...

  10. BBC评出的100本最具影响力经典书籍

    今年,英国广播公司(BBC)邀请全球35个国家共108名文化人士,参与其发起的“影响思维和历史的100部虚构故事”的推荐,要求每人最多提名 5 部作品,这些作品最终将根据提名总量排名. 该活动经过一个 ...