动规分类
- 线性动规
- 区间动规
- 树形动规
区间动规
根据题目要求,全局最优满足局部最优;
典型题例
加分二叉树(洛谷1040)
题目介绍
题目描述
设一个n 个节点的二叉树T 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。
每个节点都有一个分数(均为正整数),记第j 个节点的分数为dj。二叉树T 及它的每个子树都有 一个加分,任意一棵子树S(包括T 本身)的加分等于S 的左子树的加分×S 的右子树的加分+S的根的分数。 若某棵子树为空,规定其加分为1。叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树T。要求输出T 的最高加分和前序遍历。
输入格式
第1 行:一个整数n(n<30),为节点个数。
地2 行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
输出格式
第1 行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2 行:n个用空格隔开的整数,为该树的前序遍历。
输入样例
5
5 7 1 2 10
输出样例
145
3 1 2 4 5
题目分析
由于是中序遍历,因此永远满足左子树节点编号<根节点编号<右子树节点编号,虽是树形,但不存在选择与决策问题,因此不是树规;
在全局最优的同时,区间最优同时满足,因此选用区间dp;
动态转移方程
f[r,l]=max{f[i,k] * f[k,j]+a[i][i]}
#include<cstdio>
#include<iostream>
using namespace std;
int a[20][20],r[20][20];//r数组存储区间根节点
int n;
void find(int x,int y)//前序遍历,dfs顺序
{
if(x<=y)
{
printf("%d",&r[x][y]);
find(x,r[x][y]-1);
find(r[x][y]+1,y);
}
return;
}
int main()//区间dp,局部最优满足全局最优,当前决策具有后效性
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i][i]);
r[i][i]=i;
}
for(int i=n;i>=1;i--)//a[i][j]枚举的是从i到J的顶点;
for(int j=1+i;j<=n;j++)//枚举区间首,尾
for(int k=i;k<=j;k++)
{
if(a[i][j]<a[i][k-1]*a[k+1][j]+a[k][k])//同理,枚举区间i~k-1,k+1~j的最大值,再加上顶点自身值
{
a[i][j]=a[i][k-1]*a[k+1][j]+a[k][k];
r[i][j]=k;//将k定为当前区间根节点
}
}
printf("%d",a[1][n]);//输出的是从1~n的区间最大值;
}
线性动规
典型题例
导弹拦截,合唱队形;
以下是最长上升子序列和最长下降子序列模板
#include<cstdio>
#include<iostream>
using namespace std;
int a[10001],b[10001],c[10001];
int main()
{
int n=1;
int maxn=0,mine=0;
while(scanf("%d",&a[n])){n++;}n--;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)//最长上升
{
b[i]=1;
for(int j=1;j<=i-1;j++)
if((a[i]>=a[j])&&(b[j]+1>b[i]))b[i]=b[j]+1;
if(b[i]>maxn)maxn=b[i];
}
for(int i=n;i>=1;i--)//最长下降
{
c[i]=1;
for(int j=i+1;j<=n;j++)
{
if((a[i]<a[j])&&c[j]+1>c[i])c[i]=c[j]+1;
}
if(mine<c[i])mine=c[i];
}
printf("%d%d",maxn,mine);
return 0;
}
二分优化的最长上升子序列
#include<cstdio>
#include<iostream>
using namespace std;
int n,top,a[100005],t;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&t);
if(t>a[top])
a[++top]=t;
else
{
int low=1,high=top,mid;
while(low<=high)
{
mid=(low+high)/2;
if(a[mid]<t)
low=mid+1;
else
high=mid-1;
}
a[low]=t;
}
}
printf("%d",top);
}
树状动规
没有上司的晚会
题目介绍
有个公司要举行一场晚会。
为了能玩得开心,公司领导决定:如果邀请了某个人,那么一定不会邀请他的上司
(上司的上司,上司的上司的上司……都可以邀请)。
每个参加晚会的人都能为晚会增添一些气氛,求一个邀请方案,使气氛值的和最大。
input:
第1行一个整数N(1<=N<=6000)表示公司的人数。
接下来N行每行一个整数。第i行的数表示第i个人的气氛值x(-128<=x<=127)。
接下来每行两个整数L,K。表示第K个人是第L个人的上司。
输入以0 0结束。
output:
一个数,最大的气氛值和。
input:
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
output:
5
思路分析
本题满足树的特征,即有向无环,可以有多个后继,但只能有一个前驱,
最优化问题,当前节点的选择只与其儿子有关,满足无后效性,可以选择树状dp,于是建树,DFS;
#include<iostream>
#include<cstdio>
using namespace std;
int f[6001][2],father[6001];
bool v[6001];
int n;
int find_max(int a,int b)
{
if (a>b) return a;
else return b;
}
void dfs(int k)//f[i][0]储存不选用当前人的最大值,f[i][1]储存选用的最大值
{
v[k]=false;
for (int i=1;i<=n;i++)
if (v[i] && father[i]==k)
{
dfs(i);
f[k][0]+=f[i][1];
f[k][1]+=find_max(f[i][0],f[i][1]);
}
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&f[i][0]);
int l,k,root;
root=0;bool bk=true;
while (scanf("%d%d",&l,&k),l+k>0)
{
father[l]=k;
if (root==l || bk==true)//由于输入的无序性,因此必须找出根节点
{
root=k;bk=false;//根节点等于当前人的上司
}
}
memset(v,true,sizeof(v));
dfs(root);//从根节点开始搜索
printf("%d\n",find_max(f[root][0],f[root][1]));//输出决策是否选用第一个人(第一个人的数组已储存选与不选的最大值)
return 0;
}