版权声明:copy from zpz,我可能要修改 https://blog.csdn.net/qq_40828060/article/details/83064425

文章目录

  • 前言
    • 记忆化搜索
    • 动态规划的基本解题思路
  • 背包问题
    • 01背包
      • (待处理)P1489 猫狗大战
      • 01背包的空间优化问题
      • P1048 采药
      • P1510 精卫填海
      • P1566 加等式
      • P1504 积木城堡
    • 完全背包
      • P1474 货币系统
      • P2904 River Crossing
      • P2725 Stamps
    • 分组背包
      • P2409 Y的积木
      • P2066 机器分配
    • 二维费用背包
      • P1759 通天之潜水
      • P1586 四方定理
    • 依赖背包
      • P1064 金明的预算方案
    • 混合背包
      • P2623 物品选取
  • 坐标DP
    • P1434 滑雪
    • P1004 方格取数
    • Codevs 2853 方格游戏
    • P1508 likecloud
    • P2049 魔术棋子
  • 线性DP
    • P1057 传球游戏
    • P1754 球迷购票问题
    • P1095 守望者的逃离
    • P3399 丝绸之路
    • P1387 最大正方形
    • P1681 最大正方形2
    • P2004 领地选择
    • P2072 宗教问题
    • P1564 膜拜
    • (待处理)P2896 一起吃饭
    • (待处理)P1970 花匠
    • (待处理)P1133 教主的花园
    • (待处理)P1233 木棍加工
  • (待处理)区间DP
    • P2426 删数
    • P2858 Treats for the cows
    • P1435 回文子串
    • P4302 字符串折叠
    • P2466 Sue的小球
    • P1220 关路灯
    • P2470 压缩
  • 单调队列
    • P1886 滑动窗口
    • P1440 求m区间内的最小值
    • P2627 修剪草坪
    • (待处理)P3957 跳房子
    • (待处理)P2216 理想的正方形
  • 单调栈
    • P2782 友好城市
    • P2866 Bad Hair Day
    • P2947 Look Up
    • P4147 玉蟾宫
    • P3467 PLA-Postering
    • (待处理)P3503 KLO-Blocks
    • (不会做,弃疗)P1565 牛宫
    • (该题有BUG,弃疗)P1823 音乐会的等待
  • 二分答案+DP
    • P2370 yyy2015c01的U盘
    • (待处理)P3957 跳房子
  • 悬线法DP
    • P4147 玉蟾宫
    • P1387 最大正方形
    • P1736 创意吃鱼法
    • P1169 棋盘制作
  • 状态压缩
    • P2879 玉米田
    • P2704 炮兵阵地
    • P1896 互不侵犯
    • P3092 没有找零
    • P2915 Make up Cows
    • P3052 Cows in Skyscraper
    • P1171 售货员的难题
    • P3118 电影移动
    • (待处理)P3694 邦邦的大合唱站队
    • (待处理)P2622 关灯问题
    • (待处理)P2051 中国象棋
    • (待处理)P3959 宝藏
    • (待处理)P2595 多米诺骨牌
    • (待处理)P2453 最短距离
    • (待处理)P2167 Bill的挑战
    • (待处理)POJ 2411 Mondriaan's Dream
  • (待处理)轮廓线DP
    • P2595 多米诺骨牌
    • POJ 2411 Mondriaan's Dream
  • (待处理)数位DP
    • P2657 windy数
    • P2518 计数
    • P2602 数字计数
    • P4124 手机号码
  • (待处理)博弈论
    • P1199 三国游戏
    • P1488 肥猫的游戏
    • P2197 NIM游戏
    • P1288 取数游戏2
  • (待处理)高维DP
    • P1436 棋盘分割
    • P2489 迷宫冒险
  • (待处理)树形DP
    • 树的重心
      • P2996 拜访奶牛
      • Poj2486 Apple Tree
      • P1352 没有上司的舞会
      • P1364 医院设置
      • P2986 伟大的奶牛聚集
      • P3478 STA-Station
      • P3047 Nearby Cows
      • P1131 时态同步
      • P2014 选课
      • P2016 战略游戏
      • P2276 消防局的设立
      • (待处理)P3177 树上染色
      • (待处理)P4365 秘密袭击
      • (双倍经验)P3780 苹果树
  • (待处理)期望DP
    • P1850 换教室
    • P1291 百事世界杯之旅
    • P3412 仓鼠找sugar2
    • P3400 随机数生成器
    • P2473 奖励关

最近准备把dp完完整整的复习一遍,开博记录

前言

记忆化搜索

记忆化搜索的定义

· 不依赖任何形式的外部变量

· 答案以返回值而非参数形式存在

· 对于相同参数返回值相同

与动态规划的关系

递归实现转移,因此是反向的

如何写记忆化搜索
  • 方法一

把这道题的dp状态和方程写出来

根据他们写出dfs函数

添加记忆化数组

  • 方法二

写出这道题的暴搜程序(最好是dfs)

将这个dfs改成"无需外部变量"的dfs

添加记忆化数组

动态规划的基本解题思路

四个步骤
确定子问题
定义状态
转移方程
统计答案/避免重复求解

具体过程详见下文 P2758 编辑距离

背包问题

01背包

(待处理)P1489 猫狗大战

01背包的空间优化问题

可以空间优化的根本原因:
第i个状态仅能转移到i-1个
即当一层状态更新完毕,就不会影响其余的状态
如果正向枚举
不满足此性质

P1048 采药

记忆化搜索

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=5010,maxm=5010,inf=0x1f1f1f1f;
int n,t,val[maxn],cost[maxn],ans[maxn][maxn];
int dfs(int vleft,int step)
{if(ans[step][vleft]!=-1)return ans[step][vleft];if(step>n)return ans[step][vleft]=0;int nput=-inf,put=-inf;nput=dfs(vleft,step+1);if(cost[step]<=vleft)put=dfs(vleft-cost[step],step+1)+val[step];return ans[step][vleft]=std::max(nput,put);
}
int main()
{memset(ans,-1,sizeof(ans));scanf("%d%d",&t,&n);for(int i=1;i<=n;i++)scanf("%d%d",&cost[i],&val[i]);printf("%d",dfs(t,1));return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

递推

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=50100,maxm=50100,inf=0x1f1f1f1f;
int n,t,val[maxn],cost[maxn],ans[maxn];
int main()
{scanf("%d%d",&t,&n);for(register int i=1;i<=n;i++)scanf("%d%d",&cost[i],&val[i]);for(register int i=1;i<=n;i++)for(register int j=t;j>=cost[i];j--)ans[j]=std::max(ans[j],ans[j-cost[i]]+val[i]);printf("%d",ans[t]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

P1510 精卫填海

一开始读错了题,以为要刚好填满,就想把体积作为v,把问题转化成可行性背包
事实上该题可以把体积作为w,每当f[j]>W时,统计一下答案

#include<iostream>
#include<cstdio>
int const maxn=10101,maxm=10101,inf=0x1f1f1f1f;
int W,n,V,cv[maxn],w[maxn],f[maxn],ans;
int main()
{ans=-inf;scanf("%d%d%d",&W,&n,&V);for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&cv[i]);for(int i=1;i<=n;i++)for(int j=V;j>=cv[i];j--){f[j]=std::max(f[j],f[j-cv[i]]+w[i]);if(f[j]>=W)ans=std::max(ans,V-j);}if(ans<0){puts("Impossible");return 0;}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

P1566 加等式

模型竟然是可行性01背包求解方案数问题,完全没看出来…
题意其实是取某些数使他们的和等于集合内的某个数,每个数只能取一次,问有多少种方案,这样就很明显了
把集合内最大的数作为上限,背包必须填满
跑完背包只要把每个数对应的背包加起来即可
注意!要把自己相等的方案减去

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int t,n,f[1110],a[maxn],ans,mx;
int main()
{scanf("%d",&t);while(t--){memset(f,0,sizeof(f));ans=0;mx=-1;scanf("%d",&n);f[0]=1;for(int i=1;i<=n;i++)scanf("%d",&a[i]),mx=std::max(mx,a[i]);for(int i=1;i<=n;i++)for(int j=mx;j>=a[i];j--)f[j]+=f[j-a[i]];for(int i=1;i<=n;i++)ans+=f[a[i]];printf("%d\n",ans-n);}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

P1504 积木城堡

还算有意思的可行性01背包
然而这种水题我竟然没看出来
对于每一堆积木,都求一下可行性,用桶统计一下每个高度的可行性
当某个高度的的可行性达到n种,说明n堆积木都能凑出这个高度,作为一个可行解
从高到低枚举保证答案最优
没有可行解要特判

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1110,maxm=100110,inf=0x1f1f1f1f;
int f[maxm],cv[maxn];
int n,sum,mn=inf,cnt,ton[maxm];
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){memset(f,0,sizeof(f));f[0]=1;cnt=0,sum=0;for(int x;;){scanf("%d",&x);if(x==-1)break;cv[++cnt]=x;sum+=x;          }mn=std::min(mn,sum);for(int j=1;j<=cnt;j++)for(int k=sum;k>=cv[j];k--)f[k]=std::max(f[k],f[k-cv[j]]);for(int k=1;k<=sum;k++)ton[k]+=f[k];}for(int k=mn;k>=0;k--)if(ton[k]==n){printf("%d",k);return 0;}printf("0");return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

完全背包

P1474 货币系统

完全背包统计可行性方案数问题
其实不是特别明白…
感性理解一下就是,选择某个面值,就能获得组成当前面值的方案数
因为选择某个数,方案数是不会改变的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
int const maxn=10110,maxm=10110,inf=0x1f1f1f1f;
int n,T;
ll f[maxn],cv[maxn];
int main()
{scanf("%d%d",&n,&T);for(int i=1;i<=n;i++)scanf("%lld",cv+i);f[0]=1;for(int i=1;i<=n;i++)for(int j=cv[i];j<=T;j++)f[j]+=f[j-cv[i]];printf("%lld",f[T]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

P2904 River Crossing

比较显然的背包问题
把奶牛个数看成容积,耗时看做价值
问题转化为了可行性最小背包问题
由于同一奶牛个数可以重复选,比如可以每次只带一只奶牛所以是完全背包

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2511,maxm=2511;
int f[maxn],pre,n,m;
int main()
{memset(f,0x1f,sizeof(f));scanf("%d%d",&n,&m);f[0]=-m,pre=m;for(int w,i=1;i<=n;i++){scanf("%d",&w),pre+=w;for(int j=i;j<=n;j++)f[j]=std::min(f[j],f[j-i]+pre+m);}printf("%d",f[n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

P2725 Stamps

一开始没看到从1开始…
而且价值跟容积完全搞反了…
可行性完全背包
f[i]表示装到价值为i最少需要多少邮票
当f[j-w]>=sum时不能转移

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=54,maxm=2001000,inf=0x1f1f1f1f;
int f[maxm];
int n,sum;
int main()
{memset(f,0x1f,sizeof(f));f[0]=0;scanf("%d%d",&sum,&n);for(int w,i=1;i<=n;i++){scanf("%d",&w);for(int j=w;j<=maxm;j++){if(f[j-w]>=sum)continue;f[j]=std::min(f[j],f[j-w]+1);}}for(int i=1;i<=maxm;i++)if(f[i]==inf){printf("%d",i-1);return 0;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

分组背包

P2409 Y的积木

分组背包的可行性方案数问题
f[i][j]表示前i种的和为j的方案数
对于每一种,都跑一遍01背包
因为每个种都从上一种转移,所以每一种一定只能选一个

#include<iostream>
#include<cstdio>
#include<cmath>
int const maxn=10011,maxm=111,inf=0x1f1f1f1f;
int f[maxm][maxn],n,K,cv[maxm][maxm],maxx[maxm],minn[maxm],mx,mn;
int main()
{scanf("%d%d",&n,&K);for(int i=1;i<=n;i++){maxx[i]=-1;scanf("%d",&cv[i][0]);for(int k=1;k<=cv[i][0];k++){scanf("%d",&cv[i][k]);maxx[i]=std::max(maxx[i],cv[i][k]);}mx+=maxx[i];}f[0][0]=1;for(int i=1;i<=n;i++)for(int j=1;j<=cv[i][0];j++)for(int k=mx;k>=cv[i][j];k--)f[i][k]+=f[i-1][k-cv[i][j]];  for(int k=1;k<=mx&&K;k++){while(f[n][k]&&K){f[n][k]--,K--;printf("%d ",k);}}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

P2066 机器分配

没想到区间dp也能用分组背包水…
最优性分组背包+路径记录
分为n组,每一组的物品是选1~m个物品,价值是收益
注意!!!分组背包的滚动仅限于最优解背包,不适用方案数背包!
价值应该是w[i][k]

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=111,maxm=210,inf=0x1f1f1f1f;
int f[maxm],w[maxn][maxm],path[maxn][maxm];
int n,V;
int main()
{scanf("%d%d",&n,&V);for(int i=1;i<=n;i++)for(int j=1;j<=V;j++)scanf("%d",&w[i][j]);for(int i=1;i<=n;i++)for(int j=V;j>=1;j--)for(int k=1;k<=j;k++){int flag=0;int lst=j-k;if(f[j]<f[lst]+w[i][k]){f[j]=f[lst]+w[i][k];path[i][j]=k;for(int l=1;l<i;l++)path[l][j]=path[l][lst];flag=true;}if(flag)continue;if(f[j]==f[lst]+w[i][k]){for(int l=1;l<i;l++){if(path[l][lst]==path[l][j])continue;if(path[l][lst]<path[l][j]){flag=true;break;}}}if(flag){for(int l=1;l<i;l++)path[l][j]=path[l][lst];path[i][j]=k;}}printf("%d\n",f[V]);for(int i=1;i<=n;i++)printf("%d %d\n",i,path[i][V]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

二维费用背包

相当于有两个限制条件的背包
基本转移如下

for(int i=1;i<=n;i++)for(int j=下限1;j<=限制1;j++)for(int k=下限2;k<=限制2;k++)f[j][k]=std::max(f[j][k],f[j-cv1[i]][k-cv2[i]);
  • 1
  • 2
  • 3
  • 4

P1759 通天之潜水

裸的二维费用背包+输出路径

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=111,maxm=210,inf=0x1f1f1f1f;
int f[maxm][maxm],cv1[maxm],cv2[maxn],w[maxn],path[maxm][maxm][maxn];
int n,V1,V2;
int main()
{scanf("%d%d%d",&V1,&V2,&n);for(int i=1;i<=n;i++)scanf("%d%d%d",&cv1[i],&cv2[i],&w[i]);for(int i=1;i<=n;i++)for(int j=V1;j>=cv1[i];j--)for(int k=V2;k>=cv2[i];k--){int lst1=j-cv1[i],lst2=k-cv2[i];if(f[j][k]<f[lst1][lst2]+w[i]){f[j][k]=f[lst1][lst2]+w[i];path[j][k][0]=path[lst1][lst2][0]+1;path[j][k][path[j][k][0]]=i;for(int l=1;l<=path[lst1][lst2][0];l++)path[j][k][l]=path[lst1][lst2][l];}}printf("%d\n",f[V1][V2]);for(int i=1;i<=path[V1][V2][0];i++)printf("%d ",path[V1][V2][i]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

P1586 四方定理

二维费用方案数背包
一开始试图用Y的积木的思路写,给他分成四组
结果这道题要求不超过四个,所以不能这么做
虽然这两者很像,但还是略有不同
分组背包的状态表示的是考虑完i组限制j能得到的答案
二维费用背包表示考虑完限制i后限制j能得得到的答案
(根据这道题的特殊性,我们需要把每个限制i得到的答案加起来)

#include<cstdio>
#include<iostream>
using namespace std;
int f[5][32770],n,t;
int main()
{f[0][0]=1,n=32768;for(int i=1;i*i<=n;i++)for(int j=i*i;j<=n;j++)for(int k=1;k<=4;k++)f[k][j]+=f[k-1][j-i*i];scanf("%d",&t);while(t--){scanf("%d",&n);int ans=0;for(int k=1;k<=4;k++)ans+=f[k][n];printf("%d\n",ans);}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

依赖背包

P1064 金明的预算方案

混合背包

把上述所有背包给合起来即可

P2623 物品选取

分组背包+多重背包+完全背包

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2112,inf=0x1f1f1f1f;
int n,V,f[maxn];
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
void Pro(int a,int b)
{for(int j=V;j>=0;j--)for(int v=0;v<=j;v++){int w=a*v*v-b*v;f[j]=std::max(f[j],f[j-v]+w);}
}
void Pzo(int v,int w)
{for(int j=V;j>=v;j--)f[j]=std::max(f[j],f[j-v]+w);
}
void Pcp(int v,int w)
{for(int j=v;j<=V;j++)f[j]=std::max(f[j],f[j-v]+w);
}
void Pmu(int v,int w,int nm)
{if(v*nm>=V){Pcp(v,w);return;}int k=1;while(k<=nm){Pzo(v*k,w*k);nm-=k;k*=2;}Pzo(v*nm,w*nm);
}
int main()
{scanf("%d%d",&n,&V);for(int op,w,cv,num,i=1;i<=n;i++){scanf("%d%d%d",&op,&w,&cv);if(op==1)Pro(w,cv);else if(op==2){scanf("%d",&num);Pmu(cv,w,num);}elsePcp(cv,w);}printf("%d",f[V]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

坐标DP

P1434 滑雪

  • 记搜写法
    简单好用
 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int const dx[4]={0,0,1,-1};
int const dy[4]={1,-1,0,0};
int map[maxn][maxn],f[maxn][maxn],ans,n,m;
int cmp(int x,int y)
{return map[x]<map[y];
}
int dfs(int x,int y)
{if(x>n||x<1||y>m||y<1)return 0;if(f[x][y])return f[x][y];int nans=1;for(int p=0;p<4;p++){int nx=x+dx[p],ny=y+dy[p];if(map[x][y]>map[nx][ny])nans=std::max(nans,dfs(nx,ny)+1);}return f[x][y]=nans;
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&map[i][j]);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)ans=std::max(ans,dfs(i,j));printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 递推写法
    转移与记搜刚好相反,从低向高转移,只要排一下序就能保证某一状态仅对临近状态有贡献,从而保证无后效性
    再考虑状态,最朴素的思路是二维数组,然而不太好排序
    我们考虑把二维转成一维
    把每一行的开头接在上一行的末尾
    这样就会得到一个这样的表格
i 1 2 3 4 5 6 7 8 9 10 11 12
ai 1 2 3 4 5 16 17 18 19 6 15 24

我们发现一个数x的上下左右有这样的关系,于是就可以转移了

x-m-1 x-m x-m+1
x-1 x x+1
x+m-1 x+m x+m+1
注意边界!
当且仅当x-m≤0时,x位于最上一行;
当且仅当x+m>n*m时,x位于最下一行;
当且仅当x mod m=0时,x位于最右一行;
当且仅当(X-1) mod m =0时,x位于最左一行。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=10110,maxm=10110,inf=0x1f1f1f1f;
int const dx[4]={0,0,1,-1};
int const dy[4]={1,-1,0,0};
int map[maxn],a[maxn],ans,f[maxn];
int cmp(int x,int y)
{return map[x]<map[y];
}
int main()
{int n,m;scanf("%d%d",&n,&m);int len=n*m;for(int i=1;i<=len;i++)scanf("%d",&map[i]),a[i]=i;
//  for(int i=1;i<=len;i++)
//      printf("!!!%d %d\n",i,map[i]);std::sort(a+1,a+1+len,cmp);for(int i=1;i<=len;i++){
//      printf("%d\n",a[i]);int x=a[i];f[x]=1;if(x-m>0 && map[x]>map[x-m])f[x]=std::max(f[x],f[x-m]+1);if((x-1)%m && map[x]>map[x-1])f[x]=std::max(f[x],f[x-1]+1);if(x+m<=len && map[x]>map[x+m])f[x]=std::max(f[x],f[x+m]+1);if(x%m && map[x]>map[x+1])f[x]=std::max(f[x],f[x+1]+1);ans=std::max(ans,f[x]);}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

P1004 方格取数

  • O(n^4)做法
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=51,maxm=51,inf=0x1f1f1f1f;
int const dx[4]={0,0,-1,-1};
int const dy[4]={-1,-1,0,0};
int const dxf[4]={-1,0,-1,0};
int const dyf[4]={0,-1,0,-1};
int n,m,map[maxn][maxn],f[maxn][maxn][maxn][maxn];
int main()
{scanf("%d",&n);for(int x,y,z;;){scanf("%d%d%d",&x,&y,&z);if(!x&&!y&&!z)break;map[x][y]=z;}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=n;k++){int l=i+j-k;if(l<=0)continue;for(int p=0;p<4;p++)f[i][j][k][l]=std::max(f[i][j][k][l],f[i+dx[p]][j+dy[p]][k+dxf[p]][l+dyf[p]]);f[i][j][k][l]+=(map[i][j]+map[k][l]);if(i==k&&j==l)f[i][j][k][l]-=map[i][j];}printf("%d",f[n][n][n][n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

Codevs 2853 方格游戏

空间控制在n^3就已经够了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef unsigned long long ull;
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int const dx[4]={0,0,-1,-1};
int const dy[4]={-1,-1,0,0};
int const dxf[4]={-1,0,-1,0};
int const dyf[4]={0,-1,0,-1};
int map[maxn][maxn],f[321][maxn][maxn];
int abs(int x)
{if(x<0)return -x;return x;
}
int main()
{int n;scanf("%d",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&map[i][j]);for(int k=1;k<(n<<1);k++)for(int i=1;i<=std::min(n,k);i++)for(int j=1;j<=std::min(n,k);j++){for(int p=0;p<4;p++){if(i+dx[p]<1 || !(k-i+dy[p]) || j+dxf[p]<1 || !(k-i+dyf[p]))continue;f[k][i][j]=std::max(f[k][i][j],f[k-1][i+dx[p]][j+dxf[p]]);}f[k][i][j]+=abs(map[i][k-i+1]-map[j][k-j+1]);}printf("%d\n",f[2*n-1][n][n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

P1508 likecloud

其实这题根本就是那道时代的眼泪,那道在ioi上横空出世的一道神题
但还是写出锅来了…
边界可以直接memset
必须从第一行开始搜,因为需要给他们赋上点权
从上往下搜比较暴力,从下往上搜写崩了,等等再调吧…

  • 从上往下
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn],ans,w[maxn][maxn];
int max(int x,int y,int z)
{return std::max(std::max(x,y),z);
}
int main()
{memset(w,-0x1f,sizeof(w));scanf("%d%d",&n,&m);int x=(m/2+1);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&w[i][j]);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)f[i][j]+=max(f[i-1][j-1],f[i-1][j],f[i-1][j+1])+w[i][j];printf("%d",max(f[n][x],f[n][x+1],f[n][x-1]));return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

P2049 魔术棋子

这道题一看似乎是搜索,然而2^n肯定是过不了的
观察数据,k<=100,应该想到是关于剩余容量可能性的dp
f[i][j][k]表示i,j处是否能得到k这个数
最朴素的转移是枚举所有状态,枚举上一次的所有可能性看看能不能得到当前状态,复杂度O(nmk^2)
不过我们发现,枚举当前状态效率很低,因为有很多废状态根本不可能由上一个转移而来吗
所以我们直接枚举上一次的所有状态即可,复杂度O(nmk)稳如老狗

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=111,maxm=111,inf=0x1f1f1f1f;
int n,m,p,w[maxn][maxn],f[maxn][maxn][maxn],ans,anss[maxn];
int main()
{scanf("%d%d%d",&n,&m,&p);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&w[i][j]);f[1][1][w[1][1]%p]=1;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)for(int k=0;k<p;k++){if(f[i][j][k*(w[i][j]%p)%p])continue;f[i][j][k*(w[i][j]%p)%p]=f[i-1][j][k]|f[i][j-1][k];}for(int k=0;k<p;k++){ans+=f[n][m][k];if(f[n][m][k])anss[ans]=k;}printf("%d\n",ans);for(int i=1;i<=ans;i++)printf("%d ",anss[i]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

线性DP

P1057 传球游戏

感觉这题递推很神,于是就写了比较友善的记搜
顺便剪了个并无卵用的枝
在条件允许的时候可以两次两次跳,特殊地,有两种可能跳回原地,应注意

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn];
int dfs(int u,int step)
{u+=u<=0?n:0;u+=u>n?-n:0;if(f[u][step])return f[u][step];if(step==m)return f[u][step]=u==1;return f[u][step]=step==m-1?dfs(u+1,step+1)+dfs(u-1,step+1):dfs(u+2,step+2)+dfs(u-2,step+2)+dfs(u,step+2)*2;
}
int main()
{scanf("%d%d",&n,&m);printf("%d",dfs(1,0));return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

递推写法
一直以为两层循环1-n在第一层无法转移
结果发现如果1-m在第一层就没问题了
第二次移动一定是由第一次移动更新而来
记搜是枚举点判断次数
递推应该反过来

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n,m,f[maxn][maxn];
int dfs(int u,int step)
{u+=u<=0?n:0;u+=u>n?-n:0;if(f[u][step])return f[u][step];if(step==m)return f[u][step]=u==1;return f[u][step]=step==m-1?dfs(u+1,step+1)+dfs(u-1,step+1):dfs(u+2,step+2)+dfs(u-2,step+2)+dfs(u,step+2)*2;
}
int main()
{scanf("%d%d",&n,&m);f[1][0]=1;for(int i=1;i<=m;i++){f[1][i]=f[n][i-1]+f[2][i-1];for(int j=2;j<n;j++)f[j][i]=f[j-1][i-1]+f[j+1][i-1];f[n][i]=f[1][i-1]+f[n-1][i-1];}printf("%d",f[1][m]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

P1754 球迷购票问题

很明显,50一定在100前
问题转化为括号匹配,然后直接套卡特兰数
f[i][j]表示已经拿了i张50,j张100
转移考虑对于每一种(i,j)都能从上一张的两种选择更新

for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)f[i][j]=f[i-1][j]+f[i][j-1];
  • 1
  • 2
  • 3

只有50的状态为初始状态

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
int const maxn=501,maxm=501,inf=0x1f1f1f1f;
int n;
ll f[maxn][maxn];
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++)f[i][0]=1;for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)f[i][j]=f[i-1][j]+f[i][j-1];printf("%lld",f[n][n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

P1095 守望者的逃离

time是c的关键字!!!
事实证明能用贪心做的dp尽量分着顺序写,每一部分单独考虑

#include<iostream>
#include<cstdio>
int const maxn=300110,maxm=111,inf=0x1f1f1f1f;
int m,s,tim,f[maxn];
int main()
{scanf("%d%d%d",&m,&s,&tim);for(int i=1;i<=tim;i++){if(m>=10)f[i]=f[i-1]+60,m-=10;elsef[i]=f[i-1],m+=4;}//优先处理闪烁for(int i=1;i<=tim;i++){f[i]=std::max(f[i-1]+17,f[i]);if(f[i]>=s){puts("Yes");printf("%d",i);return 0;}}puts("No");printf("%d",f[tim]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

P3399 丝绸之路

事实证明,状态对于动态规划重要性远大于其他!
才不会说我搞了个走到第i个城市休息了j天的状态然后不会初始化就一直锅着
f[i][j]表示第i天在第j个城市处

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1011,maxm=111,inf=0x1f1f1f1f;
int n,m,np[maxn],w[maxn],f[maxn][maxn];
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&np[i]);for(int i=1;i<=m;i++)scanf("%d",&w[i]);memset(f,0x1f,sizeof(f));for(int i=0;i<=m;i++)f[i][0]=0;for(int i=1;i<=m;i++)for(int j=1;j<=n;j++)f[i][j]=std::min(f[i-1][j-1]+w[i]*np[j],f[i-1][j]);printf("%d",f[m][n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

P1387 最大正方形

水题,不过考虑到用二维前缀和直接水岂不是很low做题的初心,还是写了些递推,然而根本不会
因为最大的正方形一定不是以0结尾
所以我们可以用f[i][j]表示右下角为(i,j)的最大正方形
右下角如果为零,显然就不符合状态 if(!a[i][j]) f[i][j]=0;
对于正方形,我们肯定是想考虑他的内部填充,但对于每一次扩大出来的点,还是要处理一下,作为左右边界
因此转移就可以表示为
f[i][j]=min(f[i-1][j-1], 从当前位置向上延伸连续的1的个数, 当前位置左侧延伸连续1的个数)
简化一下会发现后两条就是对正方形边缘的处理
等价于f[i][j]=min(f[i-1][j],f[i][j-1],f[i-1][j-1])+1;


以下为二周目后的深刻理解

#include<cstdio>
#include<iostream>
int const maxn=111;
int f[maxn][maxn],n,m,ans;
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int a,j=1;j<=m;j++)scanf("%d",&a),f[i][j]=a?min(f[i-1][j],f[i][j-1],f[i-1][j-1])+a:0,ans=std::max(ans,f[i][j]);printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

P1681 最大正方形2

这题感觉跟最大正方形1一毛一样啊,就是转移条件变了变
f[i][j]表示右下角在(i,j)长度最大正方形
对于每一个f[i][j],我们都希望去考虑他的子结构
包括

  1. 内部填充
  2. 向左边延伸
  3. 向右边延伸

然后对(i,j)这个点特殊处理一下即可(显然只有(a[i][j] a[i-1][j-1]相等 a[i-1][j] a[i][j-1]相等,两对不等才合法)

转移方程

if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1]))f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;
  • 1
  • 2

最后答案记得+1,原因很显然:没有初始化

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1611,inf=0x1f1f1f1f;
int f[maxn][maxn],a[maxn][maxn],n,m,ans;
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&a[i][j]);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1]))f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;ans=std::max(f[i][j],ans);}if(!ans)ans--;//特判,原因显然是边长为1无法构成01相间的正方形printf("%d",ans+1);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

P2004 领地选择

二维前缀和的应用
先求出以(1,1)为左上角每个点为右下角的矩形的和
即求二维前缀和
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j];
由于正方形的性质
我们只需要枚举右下角的坐标(x,y)
f[i][j]表示以(i,j)为右下角(i-c,j-c)为左上角的正方形的和
转移方程:f[i][j]=b[i][j]-b[i-c][j]-b[i][j-c]+b[i-c][j-c]

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1611,inf=0x1f1f1f1f;
int f[maxn][maxn],a[maxn][maxn],b[maxn][maxn];
int lastx,lasty,n,m,c,ans=-inf;
int main()
{scanf("%d%d%d",&n,&m,&c);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)scanf("%d",&a[i][j]),b[i][j]=a[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1];for(int i=c;i<=n;i++)for(int j=c;j<=m;j++){f[i][j]=b[i][j]-b[i-c][j]-b[i][j-c]+b[i-c][j-c];if(ans<f[i][j]){ans=f[i][j];lastx=i-c,lasty=j-c;}}  printf("%d %d",lastx+1,lasty+1);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

P2072 宗教问题

这题线性结构十分明显,每次转移都要遍历寻找上一次的状态
f[i]表示前i个人至少要分成多少个集合
ff[i]表示前i个人的至少有多少点危险度
易知,每一次的转移都要寻找到上一个集合的状态
因此就有两重循环
i : 1~n 遍历所有点
j : i~1 寻找最后一个集合(也就是当前点所在的集合)的开头元素,注意当最后一个集合元素种数超过k就break
转移很简单
因为j是最后一个集合的开头,j-1就是上一个集合的结尾

f[i]=std::min(f[i],f[j-1]+1);
ff[i]=std::min(ff[i],ff[j-1]+cnt);
//其中cnt为当前集合的元素种数
  • 1
  • 2
  • 3

边界问题
而且由于j-1会访问到0,所以0要特殊处理

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1101,maxm=1101,inf=0x1f1f1f1f;
int n,V,f[maxn],ff[maxn],m,k,bel[maxm],cnt,ton[maxn];
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int main()
{scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;i++)scanf("%d",&bel[i]);memset(f,0x1f,sizeof(f));memset(ff,0x1f,sizeof(ff));f[0]=0,ff[0]=0;for(int i=1;i<=n;i++){cnt=0;memset(ton,false,sizeof(ton));for(int j=i;j>=1;j--){if(!ton[bel[j]])cnt++,ton[bel[j]]=true;if(cnt>k)break;f[i]=std::min(f[i],f[j-1]+1);ff[i]=std::min(ff[i],ff[j-1]+cnt);}}printf("%d\n%d",f[n],ff[n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

P1564 膜拜

宗教那道题的弱化版
开始直接把板子改了改码了上去,然后WA了0.5h…
后来当发现不满足条件时,不能直接break掉,因为有可能会反向来人导致条件又满足了,比如m=1时,当前有221,下一个是2,然而下4个是2111,这样条件又满足了

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2511,inf=0x1f1f1f1f;
int f[maxn],ff[maxn],ton[maxn],bel[maxn];
int n,m;
int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int abs(int x)
{return x>0?x:-x;
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&bel[i]);memset(f,0x1f,sizeof(f));f[0]=0;for(int i=1;i<=n;i++){memset(ton,0,sizeof(ton));for(int j=i;j>=1;j--){ton[bel[j]]++;if(abs(ton[1]-ton[2])<=m||!ton[1]||!ton[2])f[i]=std::min(f[i],f[j-1]+1);}}printf("%d",f[n]);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

(待处理)P2896 一起吃饭

(待处理)P1970 花匠

(待处理)P1133 教主的花园

(待处理)P1233 木棍加工

(待处理)区间DP

P2426 删数

P2858 Treats for the cows

P1435 回文子串

P4302 字符串折叠

P2466 Sue的小球

P1220 关路灯

P2470 压缩

单调队列

P1886 滑动窗口

当一个人比你小又比你强,那你就打不过他了
当一个人跟你一样强但比你小,那你就打不过他了*2

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
int const maxn=1001100,maxm=210,inf=0x1f1f1f1f;
int n,k,ans[maxn][2],a[maxn];
struct node
{int tim,val;node(int tim=0,int val=0):tim(tim),val(val){}
};
std::deque<node>q;
std::deque<node>p;
int main()
{scanf("%d%d",&n,&k);for(int x,i=1;i<=n;i++){scanf("%d",&x);while(!q.empty()&&q.back().val>=x)q.pop_back();while(!p.empty()&&p.back().val<=x)p.pop_back();q.push_back(node(i,x));p.push_back(node(i,x));while(i-k>=q.front().tim)q.pop_front();while(i-k>=p.front().tim)p.pop_front();if(i>=k)ans[i][0]=q.front().val,ans[i][1]=p.front().val;}for(int j=0;j<=1;j++){for(int i=k;i<=n;i++)printf("%d ",ans[i][j]);puts("");}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

P1440 求m区间内的最小值

强化版的单词背诵

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
int const maxn=2001000,maxm=210,inf=0x1f1f1f1f;
int n,k,a[2];
struct node
{int tim,val;node(int tim=0,int val=0):tim(tim),val(val){}
};
std::deque<node>q;
int main()
{scanf("%d%d%d",&n,&k,&a[1]);puts("0");for(int i=2;i<=n;i++){scanf("%d",&a[i%2]);while(!q.empty()&&a[(i-1)%2]<=q.back().val)q.pop_back();q.push_back(node(i-1,a[(i-1)%2]));while(i-k>q.front().tim)q.pop_front();printf("%d\n",q.front().val);}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

P2627 修剪草坪

看似水题,实则巨坑 起码坑了我2h,且听我慢慢道来
选连续不超过k个的最大值可转化为踢掉最小值,被踢的牛满足两两距离小于k
转移方程f[i]=std::max(f[i],f[j]+w[i]) i-k-1<=j<=i-1
注意边界!!!
对于i,如果选了,则前边k个可以不选,所以可以从[i-k-1,i-1]中转移
另外,本题十分毒瘤,第9个点会莫名其妙的溢出,因此直接define int long long莽上去

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#define int long long
int const maxn=101100,maxm=210,inf=0x1f1f1f1f;
int n,k;
int sum,f[maxn],mn=inf*1000;
struct node
{int tim,val;node(int tim=0,int val=0):tim(tim),val(val){}
};
std::deque<node>q;
signed main()
{scanf("%lld%lld",&n,&k);for(int i=1;i<=k+1;i++){scanf("%lld",&f[i]),sum+=f[i];while(!q.empty()&&q.back().val>=f[i])  q.pop_back();           q.push_back(node(i,f[i]));}for(int i=k+2;i<=n;i++){scanf("%lld",&f[i]),sum+=f[i];while(!q.empty()&&i-k-1>q.front().tim)  q.pop_front();f[i]+=q.front().val;while(!q.empty()&&q.back().val>=f[i])   q.pop_back();q.push_back(node(i,f[i]));}for(int i=n-k;i<=n;i++)mn=std::min(mn,f[i]);printf("%lld",sum-mn);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

(待处理)P3957 跳房子

(待处理)P2216 理想的正方形

单调栈

事实证明,单调栈就是弱化版的单调队列,它无法控制区间长度
好吧我错了,单调栈确实可以用队列来实现,但这种结构仍然称为单调栈

P2782 友好城市

按某一岸排序,在另一岸跑单调栈

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<deque>
int const maxn=201000,maxm=210,inf=0x1f1f1f1f;
int n,ans,nor[maxn],sou[maxn],a[maxn],cnt;
int cmp(int x,int y)
{return nor[x]<nor[y];
}
std::deque<int>q;
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d%d",&nor[i],&sou[i]),a[i]=i;std::sort(a+1,a+1+n,cmp);for(int i=1;i<=n;i++){while(!q.empty()&&sou[a[i]]<=q.back())q.pop_back(),cnt--;q.push_back(sou[a[i]]),cnt++;ans=std::max(ans,cnt);}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

P2866 Bad Hair Day

每次多加入一个元素就会对答案产生栈大小的贡献

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
typedef long long ll;
int const maxn=111,maxm=210,inf=0x1f1f1f1f;
int n;
ll ans;
std::stack<int>s;
int main()
{scanf("%d",&n);for(int a,i=1;i<=n;i++){scanf("%d",&a);while(!s.empty()&&a>=s.top())s.pop();ans+=s.size();s.push(a);}printf("%lld",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

P2947 Look Up

单调队列完爆单调栈的惨剧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
int const maxn=100110,maxm=210,inf=0x1f1f1f1f;
int n,ans[maxn];
struct node
{int t,v;node(int t=0,int v=0):t(t),v(v){}
};
std::deque<node>q;
int main()
{scanf("%d",&n);for(int a,i=1;i<=n;i++){scanf("%d",&a);while(!q.empty()&&a>q.back().v){ans[q.back().t]=i;q.pop_back();}q.push_back(node(i,a));}for(int i=1;i<=n;i++)printf("%d\n",ans[i]);return 0;

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

P4147 玉蟾宫

一眼望去,woc这不是悬线法嘛,定睛一看,似乎还真是单调栈的裸题…当然也是悬线法的裸题
维护一个高度单调递增的栈,每次弹栈时候更新,因为满足单增的性质,因此每个后面的元素都可以“使用”前面元素的高度,每次弹栈肯定是由于单增不满足,因此每一个被弹出栈的元素都可以“使用”当前引起弹栈的元素的高度,因此这个元素入栈时宽度为所有被弹元素之和+1

注意:每一行都是独立的,记得及时清栈

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
int const maxn=1110,maxm=1110,inf=0x1f1f1f1f;
int n,m,ans=-1,bns=-1,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm],f[maxn][maxm];
struct node
{int len,ht;node(int len=0,int ht=0):len(len),ht(ht){}
};
std::stack<node>s;
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){char a;std::cin>>a;if(a=='F')up[i][j]=up[i-1][j]+1;int l=0;while(!s.empty()&&up[i][j]<=s.top().ht){l+=s.top().len;ans=std::max(ans,l*s.top().ht);s.pop();}s.push(node(l+1,up[i][j]));}int l=0;while(!s.empty()){l+=s.top().len;ans=std::max(ans,l*s.top().ht);s.pop();}}printf("%d",ans*3);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

P3467 PLA-Postering

容易发现宽度不会影响答案
简单的性质:当两栋楼不一样高,一定需要2张海报
维护一个单调递增的队列,只需要搞一下相等的情况就好

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
int const maxn=111,maxm=210,inf=0x1f1f1f1f;
int n,ans;
std::stack<int>s;
int main()
{scanf("%d",&n);for(int qaq,a,i=1;i<=n;i++){scanf("%d%d",&qaq,&a);while(!s.empty()&&s.top()>a){ans++;s.pop();}while(!s.empty()&&s.top()==a)s.pop();s.push(a);}while(!s.empty()&&s.top()>-1){ans++;s.pop();}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

(待处理)P3503 KLO-Blocks

(不会做,弃疗)P1565 牛宫

不知道为什么枚举右端点然后把左端点进栈的做法不对…
0分代码,改天再调吧…

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
int const maxn=1110,maxm=1110,inf=0x1f1f1f1f;
int n,m,ans=-1,bns=-1,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm],f[maxn][maxm],pre[maxn][maxm];
inline int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
inline int pow(int x)
{return x*x;
}
struct node
{int pos,sum;node(int pos=0,int sum=0):pos(pos),sum(sum){}
};
std::deque<node>q;
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int a,j=1;j<=m;j++)scanf("%d",&a),pre[i][j]=pre[i][j-1]+pre[i-1][j]-pre[i-1][j-1]+a;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){int hight=j-i+1;for(int k=1;k<=m;k++){int sum=pre[j][k]-pre[i][k];int last=-1,now=0;if(!q.empty())last=q.front().pos;while(!q.empty()&&q.back().sum>sum){now=q.back().pos;q.pop_back();}ans=std::max(ans,hight*(last-now+1));q.push_back(node(k,sum));}if(!q.empty()){int last=q.front().pos,now=0;while(!q.empty()){now=q.back().pos;q.pop_back();}ans=std::max(ans,hight*(last-now+1));}}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

(该题有BUG,弃疗)P1823 音乐会的等待

样例中这一段
“1,2,2”
第二个2与1之间并没有大于2的数,然而答案并没有统计上

二分答案+DP

实质上是二分答案,然后用dp检验可行性

P2370 yyy2015c01的U盘

对于大的背包来说,接口越大价值越大,满足单调性
直接二分答案+可行性01背包check是否可行

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1101,inf=0x1f1f1f1f;
int f[maxn],cv[maxn],w[maxn];
int n,p,V,mx=-1,ans;
int check(int mid)
{memset(f,0,sizeof(f));for(int i=1;i<=n;i++){if(cv[i]>mid)continue;for(int j=V;j>=cv[i];j--)f[j]=std::max(f[j],f[j-cv[i]]+w[i]);}for(int j=V;j>=1;j--)if(f[j]>=p)return true;return false;
}
int main()
{scanf("%d%d%d",&n,&p,&V);for(int i=1;i<=n;i++)scanf("%d%d",&cv[i],&w[i]),mx=std::max(mx,cv[i]);int l=1,r=mx;while(l<=r){int mid=(l+r)/2;if(check(mid))ans=mid,r=mid-1;elsel=mid+1;}if(ans){printf("%d",ans);return 0;}printf("No Solution!");return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

(待处理)P3957 跳房子

悬线法DP

两种写法
一种是存长度,一种是存位置
其实一点差别都没有
以每个(i,j)为悬线的底,让他向左上右延伸
然而,这还不够
因为,l[i][j] 和 r[i][j] 的值都各自取决于 l[i-1][j] 和 r[i-1][j]。(因为为保证成为一个矩形,l[i][j] 不能超过 l[i-1][j],r 同理)
更新的时候顺便搞搞就好了

P4147 玉蟾宫

无法再裸

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=1110,maxm=1110,inf=0x1f1f1f1f;
int n,m,ans,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm];
char a[maxn][maxm];
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=m;i++)rit[0][i]=m+1;for(int i=1;i<=n;i++){int t=0;for(int j=1;j<=m;j++){std::cin>>a[i][j];if(a[i][j]=='R'){up[i][j]=0,lft[i][j]=0;t=j;continue;}up[i][j]=up[i-1][j]+1;lft[i][j]=std::max(t,lft[i-1][j]);}t=m+1;for(int j=m;j;j--){if(a[i][j]=='R'){rit[i][j]=m+1;t=j;continue;}rit[i][j]=std::min(t,rit[i-1][j]);}for(int j=1;j<=m;j++)ans=std::max(ans,(rit[i][j]-lft[i][j]-1)*up[i][j]);}printf("%d",ans*3);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

P1387 最大正方形

相较于玉蟾宫,多了一个限制条件

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
int const maxn=1110,maxm=1110,inf=0x1f1f1f1f;
int n,m,ans,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm],a[maxm];
std::stack<int>s;
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)rit[0][i]=n+1;for(int i=1;i<=n;i++){int pos=0;for(int j=1;j<=m;j++){scanf("%d",&a[j]);if(!a[j]){up[i][j]=lft[i][j]=0;pos=j;continue;}up[i][j]=up[i-1][j]+1;lft[i][j]=std::max(lft[i-1][j],pos);}pos=m+1;for(int j=m;j;j--){if(!a[j]){rit[i][j]=m+1;pos=j;continue;}rit[i][j]=std::min(rit[i-1][j],pos);}for(int j=1;j<=m;j++)ans=std::max(ans,std::min(rit[i][j]-lft[i][j]-1,up[i][j]));}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

P1736 创意吃鱼法

正方形的性质+悬线法+不怎么巧妙地转移

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=2511,maxm=2511,inf=0x1f1f1f1f;
int n,m,ans,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm],f[maxn][maxm],ff[maxn][maxm],a[maxm];
inline int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&a[j]);if(a[j]){up[i][j]=lft[i][j]=0;f[i][j]=min(up[i-1][j],lft[i][j-1],f[i-1][j-1])+1;ans=std::max(ans,f[i][j]);continue;}up[i][j]=up[i-1][j]+1;lft[i][j]=lft[i][j-1]+1;}for(int j=m;j;j--){if(a[j]){rit[i][j]=0;ff[i][j]=min(up[i-1][j],rit[i][j+1],ff[i-1][j+1])+1;ans=std::max(ans,ff[i][j]);continue;}rit[i][j]=rit[i][j+1]+1;}}printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

P1169 棋盘制作

棋盘的反向同奇偶染色。用于解决交错矩形问题

然后我存长度莫名爆炸
还是位置好用…

#include<iostream>
#include<cstdio>
#include<cstring>
#include<deque>
#include<stack>
int const maxn=2100,maxm=2100,inf=0x1f1f1f1f;
int n,m,ans=-1,bns=-1,lft[maxn][maxm],rit[maxn][maxm],up[maxn][maxm],f[maxn][maxm],a[maxn][maxm];
std::stack<int>s;
inline int min(int x,int y,int z)
{return std::min(std::min(x,y),z);
}
inline int pow(int x)
{return x*x;
}
inline void color(int op)
{memset(up,0,sizeof(up));memset(lft,0,sizeof(lft));memset(rit,0,sizeof(rit));for(int i=1;i<=m;i++)rit[0][i]=m+1;for(int i=1;i<=n;i++){int pos=0;for(int j=1;j<=m;j++){if(a[i][j]!=op){up[i][j]=lft[i][j]=0;pos=j;continue;}up[i][j]=up[i-1][j]+1;lft[i][j]=std::max(pos,lft[i-1][j]);}pos=m+1;for(int j=m;j;j--){if(a[i][j]!=op){rit[i][j]=m+1;pos=j;continue;}rit[i][j]=std::min(pos,rit[i-1][j]);}for(int j=1;j<=m;j++){int x=rit[i][j]-lft[i][j]-1,y=up[i][j];ans=std::max(ans,x*y);bns=std::max(bns,pow(std::min(x,y)));}}
}
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){scanf("%d",&a[i][j]);if((i+j)&1)a[i][j]=1-a[i][j];}color(1);color(0);printf("%d\n%d",bns,ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

状态压缩

简单来说状压dp就是把状态压成一个维度,直接进行状态转移
因为转移时要枚举所有状态,因此复杂度是O(2^n)

写状压一定要注意运算符的优先级问题!

  1. ! 取反
  2. 算术运算符 + - * / %
  3. 位运算符 << >>
  4. 判断运算符 > < <= >=
  5. 判断运算符 == !=
  6. 按位运算符 & | ^
  7. 逻辑运算符 && ||
  8. 条件运算符 ?:
  9. 赋值运算符 = +=&=

然而这东西太长了没啥用…

总结点性质

  1. 赋值运算符只有当右边全算完才执行所以优先级是最低的
  2. 逻辑运算符就算要短路也要等一边算完,优先级次低
  3. 算术运算符>位运算符>判断运算符>按位运算符

P2879 玉米田

状压dp的入门题,细节还是挺多的
1种0不种,将每一行作为一个状态,每行都从上一行的合法状态转移而来
合法状态的定义:
1.包含初始状态
2.满足题目的性质(对于本题来说就是1不相邻)

Map[i]表示每行的初始状态
scanf("%d",&a),Map[i]=(Map[i]<<1)+a;
pan[j]表示状态j 在本行是否合法
pan[i]=!(i&i<<1 || i&i>>1);
f[i][j]表示在第i行状态为j时的方案数

转移的时候枚举每行和当前行的状态,当状态包含初始状态且在本行合法时,枚举上一行的状态,看看是否合法

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=15,maxm=5010,inf=0x1f1f1f1f,p=100000000;
int n,m,pan[maxm],Map[maxn],f[maxn][maxm],mx;
inline void change(int x,int pos)
{for(int i=pos;i>=0;i--)printf("%d",x>>i&1);
}
//将一个数转化为二进制数
//用于查错,看看状态是否枚举正确
inline void readin()
{for(int a,i=1;i<=n;i++) for(int j=1;j<=m;j++)scanf("%d",&a),Map[i]=(Map[i]<<1)+a;for(int i=0;i<=mx;i++)pan[i]=!(i&i<<1 || i&i>>1);
}
inline void solve()
{for(int i=1;i<=n;i++)for(int j=0;j<=mx;j++)if((Map[i]&j)==j && pan[j])for(int k=0;k<=mx;k++)if(!(k&j))f[i][j]=(f[i][j]+f[i-1][k])%p;
}
int main()
{scanf("%d%d",&n,&m);mx=(1<<m)-1;f[0][0]=1;readin();solve();int ans=0;for(int i=0;i<=mx;i++)ans=(f[n][i]+ans)%p;printf("%d",ans);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

P2704 炮兵阵地

窝好菜啊…这么道水题写了好久…
这题跟玉米田几乎一毛一样
注意一点:列数少,所以应该把每一行给压起来,这样可以枚举所有列的状态!

由这题引出状压dp的时空优化
1.空间优化注意当某一行的状态仅由前几行转移而来时,可以滚动数组优化
2.枚举所有状态无疑是个大工程,所以我们不妨把合法状态给存起来,只枚举合法的
#include<iostream>
#include<cstdio>
int const maxn=111,maxm=1533;
int n,m,mx;
int Map[maxn],Sum[maxm],Pan[maxm],State[maxm],cnt;
int f[2][maxm][maxm];
inline void change(int x)
{for(int i=3;i>=0;i--)printf("%d",x>>i&1);return;
}
inline int get(int x)
{int tot=0;while(x>0){tot+=x&1;x>>=1;}return tot;
}
inline void rin()
{scanf("%d%d",&n,&m);mx=(1<<m)-1;for(int i=1;i<=n;i++){char a[12];scanf("%s",a+1);for(int j=1;j<=m;j++){Map[i]<<=1;if(a[j]=='P')Map[i]++;}}return;
}
inline void pretreatment()
{for(int i=0;i<=mx;i++){Pan[i]=!(i&i>>1||i&i>>2||i&i<<1||i&i<<2);if(Pan[i])State[++cnt]=i;Sum[i]=get(i);}
for(int i=1;i&lt;=cnt;i++)if((State[i]&amp;Map[1])==State[i])f[1][0][State[i]]=Sum[State[i]];for(int i=1;i&lt;=cnt;i++)if((State[i]&amp;Map[2])==State[i])for(int j=1;j&lt;=cnt;j++)if((State[j]&amp;Map[1])==State[j]&amp;&amp;!(State[j]&amp;State[i]))f[0][State[j]][State[i]]=std::max(f[0][State[j]][State[i]],f[1][0][State[j]]+Sum[State[i]]);
return;

}
inline void solve()
{
for(int i=3;i<=n;i++)
for(int j=1;j<=cnt;j++)
if((State[j]&Map[i])==State[j])
for(int k=1;k<=cnt;k++)
if((State[k]&Map[i-1])==State[k]&&!(State[k]&State[j]))
for(int l=1;l<=cnt;l++)
if((State[l]&Map[i-2])==State[l]&&!(State[l]&State[j])&&!(State[l]&State[k]))
f[i%2][State[k]][State[j]]=std::max(f[i%2][State[k]][State[j]],f[(i-1)%2][State[l]][State[k]]+Sum[State[j]]);
return;
}
inline void write()
{
int ans=0;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=cnt;j++)
ans=std::max(f[n%2][State[i]][State[j]],ans);
printf("%d",ans);
return;
}
int main()
{
rin();
pretreatment();
solve();
write();
return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

P1896 互不侵犯

f[i][j][k]表示是第i行状态的编号为j时总共选了k个国王
本来还想搞一发可行性背包
突然想到都枚举所有可能状态了还背个毛线包啊
遂无问津者
另,由以上三题不难看出状压dp中有关个数的部分一般都是找1的个数
若是答案就作为状态的值,若非则作为状态

#include<iostream>
#include<cstdio>
int const maxn=150,maxm=1533;
int n,limit,mx;
int Sta[maxn],cnt,Sum[maxn];
long long f[12][maxm][maxn];
inline void change(int x)
{for(int i=2;i>=0;i--)printf("%d",x>>i&1);return;
}
inline int get(int x)
{int tot=0;while(x){tot+=x&1;x>>=1;}return tot;
}
inline void rin()
{scanf("%d%d",&n,&limit);return;
}
inline void pretreatment()
{mx=(1<<n)-1;for(int i=0;i<=mx;i++)if(!(i&i<<1||i&i>>1)){Sta[++cnt]=i;Sum[cnt]=get(i);f[1][cnt][Sum[cnt]]=1;}return;
}
inline void solve()
{for(int i=2;i<=n;i++)for(int j=1;j<=cnt;j++){int now=Sta[j];for(int k=1;k<=cnt;k++){int last=Sta[k];if(!(now&last||now&last<<1||now&last>>1)){for(int l=0;l<=limit;l++)f[i][j][l+Sum[j]]+=f[i-1][k][l];}}}return;
}
inline void write()
{long long ans=0;for(int i=1;i<=cnt;i++)ans+=f[n][i][limit];printf("%lld",ans);
}
int main()
{rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

P3092 没有找零

这个题刷新了我对状压dp的认识…
详细讲讲
f[i]表示i这个状态时能买到的序号(注意从1开始按顺序买)
怎么找上一个状态呢
比如10110这个状态
这三个1都可能是这次支付的,因此上个状态就是00110 10010 10100 这三个
怎么实现呢
我们可以搞个b数组,存只支付某一张钞票的状态,如001 010 100
我们把他跟状态i与一下,就找到了某一个1
注意f[i]存的是从1可以买到的商品序号
我们发现我们可以存一个前缀和,把上一个状态的前缀和(因为浪费的钱会影响转移)加上这次支付的面额来跟各个前缀和比较得到这次的商品序号
这道题无负数,前缀和满足单调递增,可以直接二分查找
复杂度O(2^(n+1)*logm)

#include<iostream>
#include<cstdio>
int const maxn=100110,maxm=66536;
int n,m,mx,sum;
int f[maxm],pre[maxn],b[maxn],v[15],cv[maxn];
inline void change(int x)
{for(int i=2;i>=0;i--)printf("%d",x>>i&1);printf("    ");return;
}
inline int find(int x)
{int l=0,r=m,ans=0;while(l<=r){int mid=l+r>>1;if(pre[mid]<=x){ans=mid;l=mid+1;}elser=mid-1;}return ans;
}
inline void rin()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d",&v[i]),sum+=v[i];for(int i=1;i<=m;i++)scanf("%d",&cv[i]),pre[i]=pre[i-1]+cv[i];return;
}
inline void pretreatment()
{mx=(1<<n)-1;b[1]=1;for(int i=2;i<=n;i++)b[i]=b[i-1]<<1;
}
inline void solve()
{for(int i=0;i<=mx;i++)for(int j=1;j<=n;j++)if(i&b[j])f[i]=std::max(f[i],find(pre[f[i^b[j]]]+v[j]));
}
inline void write()
{int ans=-1,last=-1;for(int i=0;i<=mx;i++){if(f[i]!=m)continue;int y=0;for(int j=1;j<=n;j++)if(!(b[j]&i))y+=v[j];ans=std::max(ans,y); }printf("%d",ans);return;
}
int main()
{rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

P2915 Make up Cows

跟没有找零的转移思路有点像
都是从枚举状态i中的1,然后删掉某个1从而找到上一状态
f[i][j]表示状态i时最后一个奶牛序号是j的方案数

#include<iostream>
#include<cstdio>
int const maxn=21,maxm=66536,maxe=25100;
long long f[maxm][maxn];
int Pos[maxn],a[maxn];
int n,K,mx;
inline void change(int x)
{for(int i=4;i>=0;i--)printf("%d",x>>i&1);puts("");
}
inline int abs(int x)
{return x<0?-x:x;
}
inline void rin()
{scanf("%d%d",&n,&K);for(int i=1;i<=n;i++)scanf("%d",&a[i]);return;
}
inline void pretreatment()
{mx=(1<<n)-1;Pos[1]=1,f[1][1]=1;for(int i=2;i<=n;i++)Pos[i]=Pos[i-1]<<1,f[Pos[i]][i]=1;
}
inline void solve()
{for(int i=0;i<=mx;i++)for(int j=1;j<=n;j++){if(f[i][j])continue;if(i&Pos[j])for(int k=1;k<=n;k++){int y=i^Pos[j];if(y&Pos[k]&&abs(a[k]-a[j])>K)f[i][j]+=f[y][k];}}
}
inline void write()
{long long ans=0;for(int i=1;i<=n;i++)ans+=f[mx][i];printf("%lld",ans);
}
int main()
{rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

P3052 Cows in Skyscraper

这道题跟上边几道基本一样
f[i]表示i状态需要的最少分组
lftV[i]表示i状态能剩下的最大空间
转移的时候注意一点
f[i]更新的时候lftV[i]一定更新
f[i]不更新的lftV[i]不一定不更新!
我难道会说我就是因为忘了判新开一个组时f[i]不更新的情况而查了1h

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=21,maxm=272144,maxe=501000,inf=0x1f1f1f1f;
int n,V,mx,cv[maxn];
int Pos[maxm];
long long lftV[maxm];int f[maxm];
inline void change(int x)
{for(int i=4;i>=0;i--)printf("%d",x>>i&1);
}
inline void rin()
{scanf("%d%d",&n,&V);for(int i=1;i<=n;i++)scanf("%d",&cv[i]);return;
}
inline void pretreatment()
{mx=(1<<n)-1;Pos[1]=1;for(int i=2;i<=n;i++)Pos[i]=Pos[i-1]<<1;memset(f,0x1f,sizeof(f));for(int i=1;i<=n;i++){lftV[Pos[i]]=V-cv[i];
//      change(Pos[i]);
//      puts("");
//      printf("!%d\n",lftV[Pos[i]]);
//      puts("");f[Pos[i]]=1;}return;
}
inline void solve()
{for(int i=0;i<=mx;i++){if(f[i]!=inf)continue;for(int j=1;j<=n;j++)if(i&Pos[j]){int y=i^Pos[j];
//              change(i);
//              printf("   %d %d\n",f[i],lftV[i]);
//              change(y);
//              printf("   %d %d\n",f[y],lftV[y]);if(lftV[y]-cv[j]>=0){if(f[i]>f[y])f[i]=f[y],lftV[i]=lftV[y]-cv[j];else if(f[i]==f[y])lftV[i]=std::max(lftV[i],lftV[y]-cv[j]);}else{if(f[i]>f[y]+1)f[i]=f[y]+1,lftV[i]=V-cv[j];elseif(f[i]==f[y]+1)lftV[i]=std::max(lftV[i],lftV[y]-cv[j]);
//                  lftV[i]=std::max(lftV[i],V-cv[j]);}
//              printf("!!!%d  %d\n",f[i],lftV[i]);
//              puts("");}}return;
}
inline void write()
{
//  change(mx);
//  puts("");
//  printf("%d %d",f[mx],lftV[mx]);printf("%d",f[mx]); return;
}
int main()
{rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

P1171 售货员的难题

f[i][j]表示i状态时j为结束点的最小值
这题极限卡常,难受

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=23,maxm=1148576,maxe=501000,inf=0x1f1f1f1f;
int n,map[maxn][maxn],mx;
int Pos[maxn];
int f[maxm][maxn];
inline void rin()
{scanf("%d",&n);for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%d",&map[i][j]);return;
}
inline void pretreatment()
{memset(f,0x1f,sizeof(f)); mx=(1<<n)-1;Pos[1]=1;for(int i=2;i<=n;i++)Pos[i]=Pos[i-1]<<1;for(int i=2;i<=n;i++)f[1|Pos[i]][i]=map[1][i];
//  f[1][1]=0;return;
}
inline void solve()
{for(int i=2;i<=mx;i++)for(int j=2;j<=n;j++)if(i&Pos[j]){int y=i^Pos[j];for(int k=1;k<=n;k++)if(y&Pos[k])f[i][j]=std::min(f[i][j],f[y][k]+map[k][j]);}return;
}
inline void write()
{int ans=inf;for(int i=1;i<=n;i++)ans=std::min(ans,f[mx][i]+map[i][1]);printf("%d",ans);return;
}
int main()
{rin();pretreatment();solve();write();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

P3118 电影移动

这题不能填表!!!
像这样

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=23,maxm=1148576,maxe=501000,inf=0x1f1f1f1f;
int n,l,mx,start[maxn][1101],last[maxn],num[maxn];
int Pos[maxn];
int f[maxm];
inline void change(int x)
{for(int i=4;i>=0;i--)printf("%d",x>>i&1);puts("");
}
inline int get(int x)
{int tot=0;while(x){tot+=x&1;x>>=1;}return tot;
}
inline int find(int id,int check)
{int ans=0,l=1,r=num[id]+1;while(l<=r){int mid=l+r>>1;
//      printf("%d %d %d\n",l,r,mid);
//      printf("!!! %d %d\n",start[id][1],last[id]);if(start[id][mid]<=check&&start[id][mid]+last[id]>check){l=mid+1;ans=mid;}elser=mid-1;}return ans;
}
inline void rin()
{scanf("%d%d",&n,&l);for(int i=1;i<=n;i++){scanf("%d%d",&last[i],&num[i]);for(int j=1;j<=num[i];j++)scanf("%d",&start[i][j]);}return;
}
inline void pretreatment()
{mx=(1<<n)-1;Pos[1]=1;for(int i=2;i<=n;i++)Pos[i]=Pos[i-1]<<1;return;
}
inline void solve()
{
//  /*for(int i=0;i<=mx;i++)for(int j=1;j<=n;j++)if(i&Pos[j]){int y=i^Pos[j];int fff=find(j,f[y]);if(!fff)continue;change(i);change(i^Pos[j]);printf("!%d ",fff);printf(" %d ",start[j][fff]+last[j]-f[j]);f[i]=std::max(f[i],start[j][fff]+last[j]);printf("%d\n",f[i]);puts("");}
//          */
//  printf("%d\n",start[2][find(2,65)]);return;
}
inline void write()
{int ans=inf;for(int i=mx;i>=0;i--)if(f[i]>=l)ans=std::min(ans,get(i));if(ans==inf)printf("-1");else printf("%d",ans);return;
}
int main()
{rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

因为对于某个状态来说,其所有子状态的最优并不能保证其最优
而当前状态的合法的下一个状态的最优值一定能使答案最优
(想一想,下一场电影的合法的开始时间中最晚的值一定能使结果最优)

好!从想出填表法到疯狂对拍后发现正解是刷表法共耗时2.5h

然后写完刷表法还是只有29分…
以下是对拍数据,耗时2.5h

5 88
10 3 0 17 25
20 3 2 22 48
30 4 3 27 33 34
40 2 1 15
50 3 0 17 38
ans=3
----------------
2 25
10 3 2 10 20
20 1 0
ans=2
----------------
6 95
10 1 1
20 4 2 11 37 65
30 2 0 5
40 3 3 53 62
50 2 0 27
60 2 3 29
ans=3
----------------
6 99
10 2 2 12
20 2 1 29
30 4 0 17 44 46
40 2 3 10
50 4 0 55 114 126
60 3 1 2 26
ans=3
----------------
6 77
10 3 2 3 7
20 4 3 14 36 57
30 1 0
40 1 2
50 1 0
60 1 2
ans=3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

最大的锅是二分写炸了
统计答案要同时满足多个条件
但事实上应该满足start[id][mid]<=check就算合法的二分
只不过不一定是合法的答案而已

写完这道题感觉收获满满

#include<iostream>
#include<cstdio>
#include<cstring>
int const maxn=23,maxm=1148576,maxe=501000,inf=0x1f1f1f1f;
int n,l,mx,start[maxn][1101],last[maxn],num[maxn];
int Pos[maxn];
int f[maxm];
inline void change(int x)
{for(int i=5;i>=0;i--)printf("%d",x>>i&1);
}
inline int get(int x)
{int tot=0;while(x){tot+=x&1;x>>=1;}return tot;
}
inline int find(int id,int check)
{int ans=0,l=1,r=num[id];while(l<=r){int mid=l+r>>1;
//        printf("~~~%d\n",mid);
//        printf("!!!%d %d\n",start[id][mid]<=check,start[id][mid]+last[id]>check);
//        printf("???%d %d\n",start[id][mid],last[id]);if(start[id][mid]<=check){if(start[id][mid]+last[id]>=check)ans=mid;l=mid+1;}elser=mid-1;}
//    printf("%d\n",ans);return ans;
}
inline void rin()
{scanf("%d%d",&n,&l);for(int i=1;i<=n;i++){scanf("%d%d",&last[i],&num[i]);for(int j=1;j<=num[i];j++)scanf("%d",&start[i][j]);}return;
}
inline void pretreatment()
{mx=(1<<n)-1;Pos[1]=1;for(int i=2;i<=n;i++)Pos[i]=Pos[i-1]<<1;return;
}
inline void solve()
{
//  /*
//  puts("!!!");for(int i=0;i<=mx;i++)for(int j=1;j<=n;j++)if(!(i&Pos[j])){int y=i|Pos[j];int pos=find(j,f[i]);if(!pos)continue;f[y]=std::max(f[y],start[j][pos]+last[j]);
//              printf("~~~%d\n",j);
//              change(i);
//              printf("  %d\n",f[i]);
//              change(y);
//              printf("  %d\n",f[y]);
//              printf("!%d %d\n",pos,f[y]);
//              puts("");}
//          */
//  printf("qwq %d\n",find(2,62));
//  printf("%d %d\n",start[3][3],last[3]);return;
}
inline void write()
{int ans=inf;for(int i=mx;i>=0;i--)if(f[i]>=l)ans=std::min(get(i),ans);if(ans==inf)printf("-1");elseprintf("%d",ans);return;
}
int main()
{
//  freopen("qwq.in","r",stdin);
//  freopen("oup.out","w",stdout);rin();pretreatment();solve();write();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

最后附一个数据生成器吧,毕竟也花了不少精力在上面

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#define rii register int i
#define p 6
#define pp 100
#define ppp 4
using namespace std;
long long seed;
int n,m;
int main()
{freopen("data.in","w",stdout);//文件操作,得到输入文件 seed=time(0);srand(seed);n=rand();//windows下rand()max为32768,为了有一定的强度,我们乘一下 n*=4321;//n,m这里你也可以手动取值 n%=p;n++;m=rand();m*=4321;m%=pp;m++;printf("%d %d\n",n,m);int w=10;for(int i=1;i<=n;i++){printf("%d ",w);w+=10;int ww=(rand()*4321)%ppp+1;printf("%d ",ww);int start=ppp;int qwq=(rand()*4321)%start;for(int j=1;j<=ww;j++){printf("%d ",qwq);qwq+=(rand()*4321)%w+1;}puts("");}return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

(待处理)P3694 邦邦的大合唱站队

(待处理)P2622 关灯问题

(待处理)P2051 中国象棋

(待处理)P3959 宝藏

(待处理)P2595 多米诺骨牌

(待处理)P2453 最短距离

(待处理)P2167 Bill的挑战

(待处理)POJ 2411 Mondriaan’s Dream

(待处理)轮廓线DP

P2595 多米诺骨牌

POJ 2411 Mondriaan’s Dream

(待处理)数位DP

P2657 windy数

P2518 计数

P2602 数字计数

P4124 手机号码

(待处理)博弈论

P1199 三国游戏

P1488 肥猫的游戏

P2197 NIM游戏

P1288 取数游戏2

(待处理)高维DP

P1436 棋盘分割

P2489 迷宫冒险

(待处理)树形DP

树的重心

树的重心的定义:

树若以某点为根,使得该树最大子树的结点数最小,那么这个点则为该树的重心,一棵树可能有多个重心。

树的重心的性质:

1、树上所有的点到树的重心的距离之和是最短的,如果有多个重心,那么总距离相等。

2、插入或删除一个点,树的重心的位置最多移动一个单位。

3、若添加一条边连接2棵树,那么新树的重心一定在原来两棵树的重心的路径上。

怎么求树的重心:

定义几个数组:f[u]f[u]f[u]f[u]f[u] f[u]f[u]f[u]f[u]

版权声明: https://blog.csdn.net/qq_40828060/article/details/83064425

动态规划--from zpz相关推荐

  1. 伍六七带你学算法 动态规划 ——不同路径

    力扣 62. 不同路径 难度 中等 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为"Start" ). 机器人每次只能向下或者向右移动一步.机器人试图达到网格 ...

  2. 由动态规划计算编辑距离引发的思考

    简单介绍 编辑距离算法: https://www.cnblogs.com/BlackStorm/p/5400809.html https://wizardforcel.gitbooks.io/the- ...

  3. LeetCode 10. Regular Expression Matching python特性、动态规划、递归

    前言 本文主要提供三种不同的解法,分别是利用python的特性.动态规划.递归方法解决这个问题 使用python正则属性 import reclass Solution2:# @return a bo ...

  4. 【动态规划】Part1

    1. 硬币找零 题目描述:假设有几种硬币,如1.3.5,并且数量无限.请找出能够组成某个数目的找零所使用最少的硬币数. 分析:   dp [0] = 0            dp [1] = 1 + ...

  5. 2016.4.2 动态规划练习--讲课整理

    1.codevs1742 爬楼梯  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 小明家外面有一个长长的楼梯,共N阶.小明的腿 ...

  6. 算法设计与分析第4章 动态规划(二)【DP序列问题】

    第3章 动态规划(二)[DP序列问题] 3.2 DP序列问题 (51nod的动态规划教程很不错,讲解很详细,以下分析来自51nod) 1.矩阵取数问题 给定一个m行n列的矩阵,矩阵每个元素是一个正整数 ...

  7. 算法设计与分析第4章 动态规划(一)【背包问题】

    第3章动态规划(一)[背包问题] 基本思想: 动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,但是经分解得到的子问题往往不是互相独立的.不同子问题的数目常常只有多项式量级.在用 ...

  8. ADPRL - 近似动态规划和强化学习 - Note 7 - Approximate Dynamic Programming

    Note 7 - 近似动态规划 Approximate Dynamic Programming 7. 近似动态规划 (Approximate Dynamic Programming) 7.1 近似架构 ...

  9. ADPRL - 近似动态规划和强化学习 - Note 6 - Mitigating the Curse of Dimensionality

    Note 6 Mitigating the Curse of Dimensionality 减轻维度诅咒 6. Mitigating the Curse of Dimensionality 减轻维度诅 ...

最新文章

  1. ICCV 2013的人脸特征点检评测及代码
  2. finereport连接oracle_FINEREPORT连接远程ORACLE数据库
  3. jsp实现html注册,jsp+servlet实现最基本的注册登陆功能
  4. 4.5丢弃法 drop out
  5. 线性代数及其应用(part1)--特征向量与特征值
  6. nginx mozilla_我发现Mozilla的私人浏览模式存在重大缺陷。
  7. 灵机一动之优雅实现用例顺序插入
  8. [paper reading] GoogLeNet
  9. 工作小记20201017-hive或inceptor相关
  10. 老徐和阿珍的故事:CAP是什么?超级爱放P吗?
  11. 推文助手! 邀请码!如何填写?
  12. 基于人体感应模块的驱鸟器设计
  13. D-HARRY2020春夏新品
  14. 嵌入式软件测试(黑盒测试)-----三年嵌入式软件测试的理解
  15. 惯性室内导航入门之PDR (步行者航位推算)
  16. python爬取“微博”移动端评论数据
  17. Hotspot 重量级锁ObjectMonitor(二) 源码解析
  18. 笔记:poi学习之实现合同打印
  19. mysql update不更新_记一次MySQL更新语句update的踩坑
  20. Android 获取视频缩略图

热门文章

  1. python用while打印菱形_python 使用while循环输出*组成的菱形实例
  2. 火狐浏览器调试js技巧_充分利用Firefox的最佳技巧和调整
  3. web前端设计与开发期末作品/期末大作业(辅导6页)
  4. qqext(QQ2012显IP外挂)V1022 绿色版
  5. [高通SDM450][Android9.0]同一套代码兼容不同的emmc
  6. 某财务部门结账时发现总金额不对头。很可能是从明细上漏掉了某1笔或几笔。 如果已知明细账目清单,能通过编程找到漏掉的是哪1笔或几笔吗?
  7. html中的mata标签详解
  8. 【历史上的今天】12 月 5 日:分布式系统的“三驾马车”;世界上第一篇计算机科学博士论文;IBM 推出“深蓝”计算机
  9. WINDOWS自带的扫雷游戏作弊方法
  10. 监听页面高度变化_js监听屏幕的高度变化