概念
通过局部最优来推导出全局最优。
适用范围
1、具有最优性的子结构,一个大的最优状态必须包含着一种最优的子状态。
2、具有重叠计数性。
3、每一个子问题需要相对独立
4、存在问题的边界,可以停止DP。
基本方法
我的方法是:
1、先针对题目的条件设DP。
2、判断DP 是否满足无后效性、独立性(阶段性)。
例题
对于DP类的问题,光是将概念是无法将它理解的,我们需要在题目中真切的感受到DP思维的强大。
最长不下降子序列
题目
题目描述
有长度为N的序列:(n<=1000)
A1 A2 …..An
求最长不下降子序列:Ai1,Ai2,,,,,Aik, 其中ai1<=ai2<=…..<=aik
求最长不下降子序列的长度
输入
输出
样例输入
11
1 3 6 3 4 7 5 7 6 7 8
样例输出
8
方法
很简单,不用说了。
设f[i]选到了第i个数的最长不下降子序列长度。
答案就是f[n].
程序
var
n,i,j,max:longint;
a,f:array[1..1000] of longint;
begin
read(n);
for i:=1 to n do
read(a[i]);
for i:=1 to n do
f[i]:=1;
for i:=1 to n do
begin
for j:=1 to i-1 do
if a[i]>=a[j] then
begin
if f[i]<f[j]+1 then
f[i]:=f[j]+1;
end;
end;
max:=-maxlongint;
for i:=1 to n do
if max<f[i] then
max:=f[i];
writeln(max);
end.
【NOI2013模拟】Number (Standard IO)
题目
Time Limits: 1000 ms Memory Limits: 65536 KB Detailed Limits
Description
JYY 来到了一个新的城市,为了和大家保持联系,第一件事就是办理新的手机号。JYY 对号码的要求很高,希望大家都能够顺畅地阅读手机号,因此 JYY 特别研究了地球人的电话号码阅读习惯,有如下重大发现 (请自行代入你们的手机号码):地球人总是按照以下四种方式来朗读电话号码:
xxx-xxx-xxxxx 例如 151-958-83019
xxx-xxxx-xxxx 例如 151-9588-3019
xxxx-xxxx-xxx 例如 1519-5883-019
xxxx-xxx-xxxx 例如 1519-588-3019
即便是同一个电话号码,不同的人也会按照自己的习惯去解读,例如有些人会觉得电话号码是151-9588-3019,而有些却坚持 1519-588-3019。
为了让号码显得更上口,JYY 认为有些不吉利的数字串,在报电话号码时永远不应该被完整地读出来,例如,JYY 认为 7456 是不吉利的数字串 (气死我了),那么 13000007456 就是一个不好的号码,因为有人会把这个号码读作 130-000-07456,而在最后一次完整地读出了 7456。然而,13000745600 却不是一个不好的号码,因为无论怎么读,7456 都会被拆分开来。
现在给出 JYY 认为不吉利的数字串,请你计算满足 JYY 要求的电话号码,总共有多少个。具体来说说,一个好的电话号码满足以下要求:
电话号码是以 1 开头的 11 位数字串。
这个号码无论按照之前描述的 4 种读法中的哪个,都不能在任意时刻连续读出不吉利的数字串。
Input
输入文件第一行包括一个整数 n,表示 JYY 认为不吉利的数字串的数量。
接下来 n 行,每行一个不超过 5 的数字串,表示一个 JYY 认为不吉利的号码。
Output
输出一行,为满足条件的电话号码总数。
Sample Input
1
7456
Sample Output
9996000100
Data Constraint
40% 的数据满足 n <= 2。
100% 的数据满足 1 <= n <= 100。
题解
我们可以考虑dp.
设fi,j表示i~i+3位数字填的状态是j(j 是10进制数)
我们可以将j拆成四位数字a1,b1,c1,d1。第i位是a1、第i+1位是b1、第i+2位是c1、第i+3位是d1.
我们枚举第i+4位e1.
我们就可以得到转移:
而情况允许的条件就是,满足于e1连起来的状态(如:d1*10+e1……)中没有不合法的。
这样,答案就是
代码
#include<cstdio>
#include<cstring>
using namespace std;
long long f[9][10000];
long long bz[6][100001],n;
char st[101][6];
int main()
{
scanf("%d\n",&n);
for (int i=1;i<=n;i++)
{
scanf("%s\n",&st[i]);
int p=0;
int len=strlen(st[i]);
for (int j=0;j<=len-1;j++)
{
p=p*10+st[i][j]-'0';
}
bz[len][p]++;
}
memset(f,0,sizeof(f));
for (int j=0;j<10000;j++)
{
int a1=j/1000%10;
if (a1!=1) continue;
int b1=j/100%10;
int c1=j/10%10;
int d1=j%10;
int p=a1;
if (bz[1][p]!=0) continue;
p=b1;
if (bz[1][p]!=0) continue;
p=c1;
if (bz[1][p]!=0) continue;
p=d1;
if (bz[1][p]!=0) continue;
p=a1*10+b1;
if (bz[2][p]!=0) continue;
p=b1*10+c1;
if (bz[2][p]!=0) continue;
p=c1*10+d1;
if (bz[2][p]!=0) continue;
p=a1*100+b1*10+c1;
if (bz[3][p]!=0) continue;
p=b1*100+c1*10+d1;
if (bz[3][p]!=0) continue;
p=a1*1000+b1*100+c1*10+d1;
if (bz[4][p]!=0) continue;
f[1][j]=1;
}
int total=0;
for (int i=1;i<=7;i++)
{
for (int j=0;j<10000;j++)
{
if (f[i][j]==0) continue;
int a1=j/1000%10;
int b1=j/100%10;
int c1=j/10%10;
int d1=j%10;
int wei=i+4;
for (int e1=0;e1<=9;e1++)
{
if (bz[1][e1]!=0) continue;
if (bz[2][d1*10+e1]!=0) continue;
if (wei!=5)
{
if (bz[3][c1*100+d1*10+e1]!=0) continue;
}
if (wei!=6&&wei!=9&&wei!=5)
{
if (bz[4][b1*1000+c1*100+d1*10+e1]!=0) continue;
}
if (wei==11)
{
if (bz[5][a1*10000+b1*1000+c1*100+d1*10+e1]!=0) continue;
}
f[i+1][b1*1000+c1*100+d1*10+e1]+=f[i][j];
}
}
}
long long ans=0;
for (int j=0;j<10000;j++)
{
ans+=f[8][j];
}
printf("%lld",ans);
}
number
题目
(File IO): input:number.in output:number.out
Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits
Description
给定正整数 n,m,问有多少个正整数满足: (1)不含前导 0; (2)是 m 的倍数; (3)可以通过重排列各个数位得到 n。
Input
一行两个整数 n,m。
Output
一行一个整数表示答案对 998244353 取模的结果。
Sample Input
1 1
Sample Output
1
Data Constraint
对于 20%的数据,n<10^10。
对于 50%的数据,n<10^16,m<=20。
对于 100%的数据,n<10^20,m<=100。
题解
我们可以设fi,j,k为当前选到第i位,二进制压位n选的状态为j,对m的取模为k的方案数
设p代表新选的数。
答案为
这时我们会发现,这个时间复杂度会很大(答案还可能会计重)。
于是,我们可以考虑一下优化:
1、如果状态j中有值的位不是i个,那么可以直接判断这种情况是不合法的。这样,我们也可以省去一维i(指的是在数组里省去,时间复杂度不变)。
2、我们会发现j中有一些奇怪的潜规则:对于两个相同的数字,它的地位却是因位置而定的。这样就会导致一些多余重复计算的状态出现。所以,我们可以考虑删去这些状态。现将n的各个位数排序。如果满足存在
3、但是,我们会发现,这样的时间复杂度还是太大,所以我们需要再度进行优化。我们可以预处理出每一个状态第一个出现
这样的时间复杂度是:O(3^10*n*10*m)。
就可以拿到100分。
代码
#include<cstdio>
#include<cstring>
using namespace std;
char s[21];
long long f[59050][101];
int x,m,a[21],n,mi2[21],pd[1048577],st[1048577][21],bo[21][2];
bool bz[1048577];
void zhuan_2(int k)
{
int len=n;
int p=k;
while (k>0)
{
st[p][len]=k%2;
if (st[p][len]==1) st[p][0]++;
k=k/2;
len--;
}
}
void qsort(int l,int r)
{
int i=l;
int j=r;
int mid=a[(i+j)/2];
while (i<=j)
{
while (a[i]<mid) i++;
while (a[j]>mid) j--;
if (i<=j)
{
int t=a[i];
a[i]=a[j];
a[j]=t;
i++;
j--;
}
}
if (i<r) qsort(i,r);
if (l<j) qsort(l,j);
}
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
scanf("%s",&s);
scanf("%d",&m);
n=strlen(s);
for (int i=0;i<=n-1;i++)
{
a[i+1]=s[i]-'0';
}
qsort(1,n);
mi2[0]=1;
for (int i=1;i<=n;i++)
{
mi2[i]=mi2[i-1]*2;
}
for (int i=0;i<=mi2[n]-1;i++)
{
zhuan_2(i);
}
memset(bz,true,sizeof(bz));
int total=0;
for (int i=0;i<=mi2[n]-1;i++)
{
for (int j=2;j<=n;j++)
{
if (a[j]==a[j-1])
{
if (st[i][j-1]==0&&st[i][j]!=0)
{
bz[i]=false;
break;
}
}
}
if (bz[i]==true)
{
total++;
pd[i]=total;
}
}
f[pd[0]][0]=1;
int oo=0;
for (int j=0;j<=mi2[n]-1;j++)
{
if (bz[j]==false) continue;
for (int i=0;i<=n;i++)
{
if (i!=st[j][0]) continue;
oo++;
for (int k=1;k<=n;k++)
{
if (bo[a[k]][1]!=oo&&st[j][k]==0)
{
bo[a[k]][1]=oo;
bo[a[k]][0]=k;
}
}
for (int k=0;k<=9;k++)
{
if (i==0&&k==0&&i+1!=n) continue;
x=j;
if (bo[k][1]==oo)
{
x=x|mi2[n-bo[k][0]];
if (x==j) continue;
for (int p=0;p<=m;p++)
{
f[pd[x]][(p*10+k)%m]+=f[pd[j]][p];
f[pd[x]][(p*10+k)%m]%=998244353;
}
}
}
}
}
printf("%lld",f[pd[mi2[n]-1]][0]);
}