暑假集训第一周总结:
学习内容: 区间dp,树形dp,泛化背包,概率dp,数位dp,kmp,线段树与树状数组,矩阵快速幂,一些STL函数和容器的使用,扫描线;
1:区间dp
这类dp主要用来处理区间上求值问题 比如 最优三角形剖分问题 :
将一个凸多边形分成n个三角形 问划线的最短长度 zoj3537 poj 1179
for (i =0; i < n; ++i)
for (j = i +2; j < n; ++j) //这里注意相邻点不需划线
cost[i][j] = cost[j][i] = Count(save[i],save[j]);
for (i =0; i < n; ++i) {
for (j =0; j < n; ++j)
dp[i][j] = INF;
dp[i][(i+1)%n] =0; //仍然 相邻点dp值为0 这里编号1与编号n相邻
}
for (i = n -3; i >= 0; --i)//注意这三个for循环的顺序
for (j = i +2; j < n; ++j)//因为要保证在算dp[i][j]时dp[i][k]和dp[k][j]时已经计算,所以i为逆序,j要升序
for (k = i +1; k <= j - 1; ++k)
dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]+cost[i][k]+cost[k][j]);
思想:类似于遍历每一种情况的暴力结合递归or递推 在三层for循环的过程中 已经计算出每两点之间划线的最小长度 dp[1][n]=dp[1][k]+dp[k][n]+cost[i][k]+cost[k][j];
2:树形dp
树形dp主要用来解决 取点时有前置条件 比如有前置点 比如与父亲节点不能同时出现 比如只在n个点取m个点 这时dp的状态一般为二维
dp[i][j] 通常表示 在以i为根节点的树下取j个点的值 dp[i][1],dp[i][0] 通常表示 在以i为根节点的树下 不取i的最大值和取了i的最大值
典型例题 hdu 1520 hdu 1561 hdu 1054 hdu 2412 poj 1947
树形dp的类型很多 如求能取到的最多点 能取到的最大价值 取n个点的最大价值
void bfs(int i)
{
if(visit[i]) return;
visit[i]=1;
dp[i][1]=a[i];
for(int x=head[i];x!=-1;x=e[x].next)
{
if(visit[e[x].to]!=1)
{
bfs(e[x].to);
dp[i][1]+=dp[e[x].to][0];
dp[i][0]+=max(dp[e[x].to][0],dp[e[x].to][1]);
}
}
} //临接表存图的深搜 一直处理到子结点 然后逐层向上更新 最后得到需要的值
临接表模版:
struct data
{
int to;
int next;
}e[12010];
int head[6010],
for(i=1;i<=n;i++)
{
scanf("%d%d",&p,&q);
if(p==0&&q==0)break;
e[top].to=p;
e[top].next=head[q];
head[q]=top++;
e[top].to=q;
e[top].next=head[p];
head[p]=top++;
}
void bfs(int i)
{
if(visit[i]) return;
visit[i]=1;
dp[i][1]=a[i];
for(int x=head[i];x!=-1;x=e[x].next)
{
if(visit[e[x].to]==0)
{
bfs(e[x].to);
for(int j=m;j>=1;j--)
for(int k=1;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[e[x].to][k]);
}
}
} //结合泛化背包的取点问题 注意三层循环顺序 也是泛化背包的顺序
3:泛化背包
多与树形dp结合 主要体现一种任务分配思想 eg: 有三种任务 每种任务投入时间不同收益不同 现时间确定 问如何安排活动能取得最大价值
hdu 1712 典型的泛化背包。
for(i=1;i<=n;i++)
for(j=m;j>=1;j--)
for(k=j;k>=1;k--)
dp[j]=max(dp[j],dp[j-k]+a[i][k]);
dp[j]指消耗j点体力可以得到的收益 转移方程指 dp[j]可能由 dp[j-k]+第i种任务消耗k时间
4: 概率dp
接触到了两种类型 一种是求期望 一种是求步数;
求期望需要正向推导 求步数反向推导
类似于 已知第一步走到x1-xn的概率 问走到xm的概率或期望 这时候需要正向递推 每一个xi的概率等于之前每个能走到xi的点的概率乘走到xi的概率 再求和
另一种情况 已知现在在xm点 问从x1走到那个点的步数 这时候dp[xm]=0; 每个能到xm的点的步数等于1+走到xm的概率*dp[xm]; 可形象理解成 走了一步后后面还有1/6概率走1步到达 4/6概率走2步到达 1/6概率走三步到达 最后一直推导起点。
for(i=n-1;i>=0;i--)
{
if(father[i]!=-1) dp[i]=dp[father[i]];
else
{
for(j=1;j<=6;j++)
{
if(i+j<=n) dp[i]+=(double)dp[i+j]/6;
else break;
}
dp[i]+=1.0;
}
}