P1018 乘积最大
一串数字,加一些乘号使其乘积最大
很显然是裸的区间dp,然后60分。。。。。因为没有高精
int a[100];
int dp[100][100];
int getnum(int x,int y)
{
int ans=0;
for(int i=x;i<=y;i++)
{
ans=ans*10+a[i];
}
return ans;
}
main(void)
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%1d",&a[i]);
}
for(int i=1;i<=n;i++)dp[i][0]=getnum(1,i);
for(int i=1;i<=n;i++)
for(int j=k;j>=1;j--)
for(int p=i-1;p>=1;p--)
{
dp[i][j]=max(dp[i][j],dp[i-p][j-1]*getnum(i-p+1,i));
}
cout<<dp[n][k];
}
P1057 传球游戏
两个属性,传球号码,传球次数,找出前后的关系即可
int n,M;
int dp[35][35];
main(void)
{
cin>>n>>M;
dp[0][0]=1;
for(int m=1;m<=M;m++)
for(int i=0;i<n;i++)
{
dp[i][m]=dp[(i-1+n)%n][m-1]+dp[(i+1+n)%n][m-1];
//printf("i:%d m:%d=%d\n",i,m,dp[i][m]);
}
cout<<dp[0][M];
}
CF414B Mashmokh and ACM
如果一个数列中,后一个数都能被前面一个数整除,那么就叫这个数列为好数列。输入n,k,求数列中最大元素为n,数列长度为k的好数列的种数(对1000000007取模)
找前面的整除比较难找,因此转化为递推后边的乘法
const int p=1000000007;
int dp[2005][2005];
main(void)
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)dp[i][1]=1;
for(int i=1;i<=n;i++)//最后一个数
for(int j=1;j<=k;j++)//数列的个数
{
for(int ch=1;ch<=n/i;ch++)//枚举可以乘的数,后边的++
{
dp[i*ch][j+1]+=dp[i][j]%p;
dp[i*ch][j+1]%=p;
}
}
int ans=0;
for(int i=1;i<=n;i++)//把以所有i结尾的长度为k的加起来
{
ans+=dp[i][k]%p;
}
cout<<ans%p;
}
P1077 摆花
题目描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第ii种花不能超过a[i]盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
隐藏的背包问题,需要自己找到价值和容量。
定义物品的种类和数列为i,a[i],每一个的价值为1,容量为m,求的的最大价值的方案数就是容量为m的方案数
int n,m;
const int p=1000007;
int a[200];
int aa[100000];
int cnt=0;
int dp[200][200];
main(void)
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i][0]=1;
}
dp[0][0]=1;
//前i盆,一共摆了j盆
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
for(int k=0;k<=min(j,a[i]);k++)
dp[i][j]+=dp[i-1][j-k]%p;
dp[i][j]%=p;
// printf("i:%d-j:%d-dp:%d\n",i,j,dp[i][j]);
}
cout<<dp[n][m]%p;
}
P1586 四方定理
与上一题类似,也可以转化为背包问题,但是多重背包,可定义每个平方数的价值为i,两个容量4,和要求的n。这样通过定义价值,把价值和满容量联系起来,可以方便的求得满容量(即最大价值)的方案数,就是要求的结果。
int a[200];
int dp[32769][5];
int f[32769];
main(void)
{
int t;
cin>>t;
int n=0;
for(int i=1;i<=t;i++)
{
cin>>a[i];
n=max(a[i],n);
}
dp[0][0]=1;
for(int i=1;i<=181;i++)
for(int j=i*i;j<=n;j++)
for(int k=1;k<=4;k++)
{
dp[j][k]+=dp[j-i*i][k-1];
}
for(int i=1;i<=t;i++)
{
int ans=0;
for(int j=1;j<=4;j++)
{
ans+=dp[a[i]][j];
}
printf("%d\n",ans);
}
}
P1005 矩阵取数游戏
思路:每一行都是独立的,可以分别处理。
dp[i][j]为剩下[i,j]段时的价值,然后处理每一个剩下一个数的价值
int dp[100][100];
int a[100][100];
main(void)
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
int anss=0;
for(int k=1;k<=n;k++)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=m;i++)
for(int j=m;j>=i;j--)
{
if(i==1&&j==m)continue;
if(i-1==0)dp[i][j]=dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1));
else if(j+1==m+1)dp[i][j]=dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1));
else dp[i][j]=max(dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1)),dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1)));
// printf("%d %d %d\n",i,j,dp[i][j]);
}
int ans=0;
for(int i=1;i<=m;i++)
{
ans=max(dp[i][i]+a[k][i]*(1<<(m)),ans);
}
anss+=ans;
}
cout<<anss;
}
P2426 删数
与上一个题目类似,但是这次不是一个个取得,因此要把dp[i][j]定义为(i,j)段的最大价值
这两个题目都不是直接求得了答案,而是通过区间的压缩到达一个方便判断的状态。
int n,a[200],dp[200][200];
main(void)
{
int n;
cin>>n;
_1for(i,n)cin>>a[i];
for(int i=0;i<=n+1;i++)
for(int j=n+1;j>=i;j--)
{
if(i==0&&j==n+1)continue;
if(i==j)continue;
for(int k=1;k<=i;k++)//k----i
{
int index=abs(a[k]-a[i])*(i-k+1);
if(k==i)index=a[k];
dp[i][j]=max(dp[i][j],dp[k-1][j]+index);
}
for(int k=n;k>=j;k--)//j----k
{
int index=abs(a[j]-a[k])*(k-j+1);
if(k==j)index=a[k];
dp[i][j]=max(dp[i][j],dp[i][k+1]+index);
}
//printf("%d-%d-%d\n",i,j,dp[i][j]);
}
int ans=0;
for(int i=0;i<=n;i++)
{
ans=max(dp[i][i+1],ans);
}
cout<<ans;
}
P1040 加分二叉树
按照dp的思路,这是枚举中点,是一个裸的区间dp。按照dfs的思路,每次枚举中点分治
dfs
int root[500][500];
int mid[1000];
int m[500][500];
int dfs(int l,int r)
{
if(m[l][r])return m[l][r];
int ans=0;
if(l>r)return 1;
if(l==r)
{
root[l][r]=l;
m[l][r]=mid[l];
return mid[l];
}
for(int i=l;i<=r;i++)
{
int t=mid[i]+dfs(l,i-1)*dfs(i+1,r);
if(t>ans)
{
root[l][r]=i;
ans=t;
m[l][r]=ans;
}
}
return ans;
}
void print(int l,int r)
{
if(l>r)
return ;
printf("%d ",root[l][r]);
print(l,root[l][r]-1);
print(root[l][r]+1,r);
}
main(void)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>mid[i];
}
cout<<dfs(1,n)<<endl;
print(1,n);
}
dp
int n,v[500],f[500][500],root[500][500];
void print(int l,int r)
{
if(l>r)return ;
if(l==r)
{
printf("%d ",l);return ;
}
printf("%d ",root[l][r]);
print(l,root[l][r]-1);
print(root[l][r]+1,r);
}
main(void)
{
cin>>n;
_1for(i,n)cin>>v[i];
for(int i=1;i<=n;i++)
{
f[i][i]=v[i];
f[i][i-1]=1;
}
for(int i=n;i>=1;i--)
{
for(int j=i+1;j<=n;j++)
{
for(int k=i;k<=j;k++)
{
if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k])
{
f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
root[i][j]=k;
}
}
}
}
cout<<f[1][n]<<endl;
print(1,n);
}
上一个题感觉像一个假的树形dp,接下来介绍几种常见的树形dp。参考
1.最大独立子集
最大独立子集的定义是,对于一个树形结构,所有的孩子和他们的父亲存在排斥,也就是如果选取了某个节点,那么会导致不能选取这个节点的所有孩子节点。
没有上司的舞会
int dp[6005][2];//dp[i][0]是不要i的最值,dp[i][1]是要i的最值
vector<int >g[6005];
int a[6005];
int m[6005];
int find(int now,int pre)
{
int fa,son;
int len=g[now].size();
dp[now][1]=a[now];
dp[now][0]=0;
for(int i=0;i<len;i++)
{
if(g[now][i]==pre)continue;//避免重复
son=find(g[now][i],now);//得到儿子的dp值,儿子其实是g[now][i],没有return也可以
dp[now][1]+=dp[son][0];
dp[now][0]+=max(dp[son][0],dp[son][1]);
}
return now;
}
main(void)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int x,y;
for(int i=1;i<=n-1;i++)
{
cin>>y>>x;
g[x].push_back(y);//双向建边,但是要注意不能往复遍历,因此函数要记录上一个的遍历
g[y].push_back(x);
}
int f=find(1,0);//从1开始遍历
printf("%d",max(dp[f][0],dp[f][1]));//因为dp[f][]是最后遍历的(先是儿子),所以输出这个即可
}
P1122 最大子树和
与上一题类似,但是这次不要儿子,孙子也不能要了。
这两者都可以用拓扑结构递推来做
int dp[16001][2];
int a[16001];
vector<int >g[16001];
void ss(int now,int pre)
{
dp[now][1]+=a[now];
int len=g[now].size();
for(int i=0;i<len;i++)
{
int son=g[now][i];
if(son==pre)continue;
ss(son,now);
dp[now][1]+=max(dp[son][0],dp[son][1]);
}
}
main(void)
{
int n,x,y;
cin>>n;
_1for(i,n)cin>>a[i];
_1for(i,n-1)
{
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
ss(1,0);
int ans=0;
for(int i=1;i<=n;i++)
{
ans=max(dp[i][1],ans);
}
cout<<ans;
}
P2513 [HAOI2009]逆序对数列
题解
我的思路是dp[i][j]插入一个数字以后dp[i+1][j+k]增加,t了,写出表达式可以发现有一个求和的过程,可以将缩短
const int p=10000;
int dp[1000][100001];
main(void)
{
int n,k;
cin>>n>>k;
dp[1][0]=1;
for(int i=1;i<n;i++)
for(int j=0;j<=i*(i-1)/2;j++)
{
for(int k=0;k<=i;k++)
dp[i+1][j+k]+=dp[i][j]%p;
}
cout<<dp[n][k]%p;
}
正解
const int p=10000;
int f[1001][1001];
main(void)
{
int n,k;
cin>>n>>k;
f[1][0]=1;
for(int i=2;i<=n;i++)
{
int sum=0;
for(int j=0;j<=k;j++)
{
sum+=f[i-1][j]%p;
f[i][j]=sum%p;
if(j>=i-1)
{
sum-=f[i-1][j-i+1]%p;
sum=(sum+p)%p;
}
}
}
cout<<f[n][k]%p;
}