1.简单线性dp问题
题目1:01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=1010;
int w[N],v[N];
int f[N][N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){//初始值取零取一取决于是否合法
f[i][j]=f[i-1][j];//左半边的子集
if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//右半边的子集,有可能不存在
}
}
cout<<f[n][m]<<endl;
return 0;
}
题目2:地宫寻宝
X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。
宫的入口在左上角,出口在右下角。
小明被带到地宫的入口,国王要求他只能向右或向下行走。
走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。
当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。
请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。
输入格式
第一行 3 个整数,n,m,k,含义见题目描述。
接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。
输出格式
输出一个整数,表示正好取 k 个宝贝的行动方案数。
该数字可能很大,输出它对 1000000007 取模的结果。
数据范围
1≤n,m≤50,
1≤k≤12,
0≤Ci≤12
#include<iostream>
#include<string>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=55,MOD=1000000007;
int f[N][N][13][14];
int w[N][N];
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>w[i][j];
w[i][j]++;
}
}
f[1][1][1][w[1][1]] = 1;
f[1][1][0][0] = 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1&&j==1) continue;
for(int u=0;u<=k;u++){
for(int v=0;v<=13;v++){
int &val = f[i][j][u][v];
val=(val+f[i-1][j][u][v])%MOD;
val=(val+f[i][j-1][u][v])%MOD;
if(u>0&&v==w[i][j]){
for(int c=0;c<v;c++){
val=(val+f[i-1][j][u-1][c])%MOD;
val=(val+f[i][j-1][u-1][c])%MOD;
}
}
}
}
}
}
int res=0;
for (int i = 0; i <= 13; i ++ ) res = (res + f[n][m][k][i]) % MOD;
cout<<res;
return 0;
}
题目三:波动数列
观察这个数列:
1 3 0 2 -1 1 -2 …
这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。
栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?
输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。
输出格式
共一行,包含一个整数,表示满足条件的方案数。
由于这个数很大,请输出方案数除以 100000007 的余数。
数据范围
1≤n≤1000,
−109≤s≤109,
1≤a,b≤106
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010,MOD = 100000007;
int f[N][N];
int get_mod(int a,int b){
return (a%b+b)%b;
}
int main(){
int n,s,a,b;
cin>>n>>s>>a>>b;
f[0][0]=1;
for(int i=1;i<n;i++){
for(int j=0;j<n;j++){
f[i][j]=(f[i-1][get_mod(j-a*(n - i),n)]+f[i-1][get_mod(j+(n - i)*b,n)])%MOD;
}
}
cout<<f[n-1][get_mod(s,n)];
return 0;
}
2、完全背包问题
题目1:
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
int f[N][N],v[N],w[N];
int n,V;
int main(){
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++){
scanf("%d%d",&v[i],&w[i]);
}
f[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=V;j++){
// for(int k=0;k*v[i]<=j;k++){
// f[i][j]=max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]);
// }这样写会爆超时,我们要进行优化;
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j-v[i]]+w[i],f[i-1][j]);
}
}
printf("%d",f[n][V]);
}
题目2:包子凑数
小明几乎每天早晨都会在一家包子铺吃早餐。
他发现这家包子铺有 N 种蒸笼,其中第 i 种蒸笼恰好能放 Ai 个包子。
每种蒸笼都有非常多笼,可以认为是无限笼。
每当有顾客想买 X 个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有 X 个包子。
比如一共有 3 种蒸笼,分别能放 3、4 和 5 个包子。
当顾客想买 11 个包子时,大叔就会选 2 笼 3 个的再加 1 笼 5 个的(也可能选出 1 笼 3 个的再加 2 笼 4 个的)。
当然有时包子大叔无论如何也凑不出顾客想买的数量。
比如一共有 3 种蒸笼,分别能放 4、5 和 6 个包子。
而顾客想买 7 个包子时,大叔就凑不出来了。
小明想知道一共有多少种数目是包子大叔凑不出来的。
输入格式
第一行包含一个整数 N。
接下来 N 行,每行包含一个整数 Ai。
输出格式
输出一个整数代表答案。
如果凑不出的数目有无限多个,输出INF。
数据范围
1≤N≤100,
1≤Ai≤100,
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=110;
const int maxd=10010;
int w[N];
bool f[N][maxd];//
int n;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main(){
scanf("%d",&n);
int d=0;//代表最大公约数;
for(int i=1;i<=n;i++){
scanf("%d",&w[i]);
d=gcd(d,w[i]);
}
if(d!=1)puts("INF");
else {
f[0][0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<=maxd;j++){
f[i][j]=f[i-1][j];
if(j>=w[i]) f[i][j]|=f[i][j-w[i]];
}
}
int res=0;
for(int i=0;i<maxd;i++){
if(!f[n][i]) res++;
}
printf("%d\n",res);
}
return 0;
}
3、 区间dp
题目1:密码脱落
X星球的考古学家发现了一批古代留下来的密码。
这些密码是由A、B、C、D 四种植物的种子串成的序列。
仔细分析发现,这些密码串当初应该是前后对称的(也就是我们说的镜像串)。
由于年代久远,其中许多种子脱落了,因而可能会失去镜像的特征。
你的任务是:
给定一个现在看到的密码串,计算一下从当初的状态,它要至少脱落多少个种子,才可能会变成现在的样子。
输入格式
共一行,包含一个由大写字母ABCD构成的字符串,表示现在看到的密码串。
输出格式
输出一个整数,表示至少脱落了多少个种子。
数据范围
输入字符串长度不超过1000
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
char s[N];
int f[N][N];//表示集合的最大子序列(回文串);
int main(){
scanf("%s",s);
int n=strlen(s);//区间循环,先循环区间长度,再循环左,算出右
for(int len=1;len<=N;len++){
for(int l=0;l+len-1<n;l++){
int r=l+len-1;
if(len==1) f[l][r]=1;
else{
if(s[l]==s[r]) f[l][r]=f[l+1][r-1]+2;
if(f[l+1][r]>f[l][r]) f[l][r]=f[l+1][r];
if(f[l][r-1]>f[l][r]) f[l][r]=f[l][r-1];
}
}
}
printf("%d", n-f[0][n-1]);
return 0;
}
题目二:括号配对
Hecy 又接了个新任务:BE 处理。
BE 中有一类被称为 GBE。
以下是 GBE 的定义:
空表达式是 GBE
如果表达式 A 是 GBE,则 [A] 与 (A) 都是 GBE
如果 A 与 B 都是 GBE,那么 AB 是 GBE
下面给出一个 BE,求至少添加多少字符能使这个 BE 成为 GBE。
注意:BE 是一个仅由(、)、[、]四种字符中的若干种构成的字符串。
输入格式
输入仅一行,为字符串 BE。
输出格式
输出仅一个整数,表示增加的最少字符数。
数据范围
对于所有输入字符串,其长度小于100。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=110;
int f[N][N];
const int INF=1e9;
string s;
bool is_match(char a,char b){
if(a=='('&&b==')') return true;
if(a=='['&&b==']') return true;
return false;
}
int main(){
cin>>s;
int n=s.size();//字符串的长度;
for(int len=1;len<=n;len++){
for(int i=0;i+len-1<n;i++){
int j=i+len-1;
f[i][j]=INF;
if(is_match(s[i],s[j])) f[i][j]=f[i+1][j-1];
if(j>=1) f[i][j]=min(f[i][j],min(f[i+1][j],f[i][j-1])+1);
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
}
}
cout<<f[0][n-1];
return 0;
}
4、树形dp问题
题目1:生命之树
题目2:旅游规划
5、矩阵,快速幂优化的dp问题
题目1:斐波那契数列
大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f4=3,…,fn=fn−1+fn−2。
现在问题很简单,输入 n 和 m,求 fn 的前 n 项和 Snmodm。
输入格式
共一行,包含两个整数 n 和 m。
输出格式
输出前 n 项和 Snmodm 的值。
数据范围
1≤n≤2000000000,
1≤m≤1000000010
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 3;
int n, m;
void mul(int c[], int a[], int b[][N])
{
int temp[N] = {0};
for (int i = 0; i < N; i ++ )
for (int j = 0; j < N; j ++ )
temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;
memcpy(c, temp, sizeof temp);
}
void mul(int c[][N], int a[][N], int b[][N])
{
int temp[N][N] = {0};
for (int i = 0; i < N; i ++ )
for (int j = 0; j < N; j ++ )
for (int k = 0; k < N; k ++ )
temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;
memcpy(c, temp, sizeof temp);
}
int main()
{
cin >> n >> m;
int f1[N] = {1, 1, 1};
int a[N][N] = {
{0, 1, 0},
{1, 1, 1},
{0, 0, 1}
};
n -- ;
while (n)
{
if (n & 1) mul(f1, f1, a); // res = res * a
mul(a, a, a); // a = a * a
n >>= 1;
}
cout << f1[2] << endl;
return 0;
}
题目二:垒骰子
赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体。
经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥!
我们先来规范一下骰子:1 的对面是 4,2 的对面是 5,3 的对面是 6。
假设有 m 组互斥现象,每组中的那两个数字的面紧贴在一起,骰子就不能稳定的垒起来。
atm想计算一下有多少种不同的可能的垒骰子方式。
两种垒骰子方式相同,当且仅当这两种方式中对应高度的骰子的对应数字的朝向都相同。
由于方案数可能过多,请输出模 109+7 的结果。
输入格式
第一行包含两个整数 n,m,分别表示骰子的数目和排斥的组数。
接下来 m 行,每行两个整数 a,b,表示 a 和 b 数字不能紧贴在一起。
输出格式
共一个数,表示答案模 109+7 的结果。
数据范围
1≤n≤109,
1≤m≤36,
1≤a,b≤6
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6,mod=1e9+7;
typedef long long LL;
int a[N][N];//快速幂的矩阵
int n,m;
int get_opp(int x){
if(x>=3) return x-3;
return x+3;
}
void mul(int c[][N],int a[][N],int b[][N]){
int temp[N][N];
memset(temp, 0, sizeof temp);
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
for(int k=0;k<N;k++){
temp[i][j]=(temp[i][j]+(LL)a[i][k]*b[k][j])%mod;
}
}
}
memcpy(c,temp,sizeof temp);
}
int main(){
cin>>n>>m;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
a[i][j]=4;
}
}
while(m--){
int x,y;
scanf("%d%d",&x,&y);
x--,y--;
a[x][get_opp(y)]=0;
a[y][get_opp(x)]=0;//将限制条件加到快速幂矩阵当中去;
}
n--;
int f[N][N]={4,4,4,4,4,4};//将一维的数组拓展成为二维的数组,便于计算;
while(n){
if(n&1) mul(f,f,a);//f=f*a;
mul(a,a,a);//a=a*a;
n>>=1;//n=n/2;
}
int res=0;
for(int i=0;i<N;i++) res=(res+f[0][i])%mod;
printf("%d\n",res);
return 0;
}