zoukankan      html  css  js  c++  java
  • DP学习笔记

    前言

    因为自己学的快忘的也快,故在此记录一些,有待补充。

    目录

    1.背包问题
    2.区间DP
    3.树形DP
    4.状压DP

    线性DP

    感觉范围很广,没有什么明显的特征,大概简单的大部分人都会吧,不想讲什么。其实是自己水平不够

    背包问题

    弱鸡只会01背包(bushi),总之是个只能背背板子的蒟蒻。

    for(int i=1;i<=n;i++)
    for(int j=m;j>=v[i];j--)
    f[j]=max(f[j],f[j-v[i]]+w[i]);
    

    记得要倒序枚举,不然一个物品会被用多次,如果正序枚举就变成完全背包了。

    还有个二维01背包,就多加一个维度,没什么好说的。

    多重背包似乎要二进制优化?打算等学了单调队列直接跳到最优,就先不讲了。分组背包没有做过题,但是树上背包似乎和它比较像,这也不讲。

    然后好像就没什么背包了,总之就这么水过去了吧……

    区间DP

    由于是有点久之前学的了,所以忘得可能有点多,能写多少写多少吧。

    经典例题:上古NOI真题石子合并

    口胡分析:观察题目可以看出此具有明显的区间合并性质,可以划分成一个一个区间,然后维护区间的信息,通过DP求解。

    for(int L=2;L<=n;L++)
    for(int i=1;i<=2*n-L+1;i++)
    {
    	j=i+L-1;
    	fmin[i][j]=1e9;
    	for(int k=i;k<j;k++)
    	{
    		fmin[i][j]=min(fmin[i][j],fmin[i][k]+fmin[k+1][j]+sum[j]-sum[i-1]);
    		fmax[i][j]=max(fmax[i][j],fmax[i][k]+fmax[k+1][j]+sum[j]-sum[i-1]);
    	}
    }
    

    嗯,区间DP的题一般都比较模板,只有内部的状态转移方程要变,所以可以背板子了。

    注意要先枚举区间长度,然后枚举得到两个端点,再枚举这个区间是由那两个区间合并而来的就行了。

    经典断环成链的技巧不要忘记,就是把长度复制成两倍,接着再一个个枚举断开的地方就行。

    NOIP真题能量项链,跟石子合并一样板子。

    进阶指南上的例题[IOI1998]Polygon是比较好的,有思维含量,值得一做。

    二维区间DP以后再学,先立个flag在这。

    树形DP

    这块大概是NOIP关于DP最重要的部分了,毕竟树这东西总是受到很多人青睐,而且花样也格外的多。

    经典例题一:没有上司的舞会

    最简单的树形DP了,只要用一个f数组表示每个人和他的所有下属创造的最大价值,并进行简单的分类讨论即可。

    f[x][0]表示此人不来,那么可以在它的下属来与不来里选一个价值最大的加上去
    也就是f[x][0]+=max(f[y][0],f[y][1]),其中y是x的下属之一。

    f[x][1]表示此人会来,那么他的下属肯定不会来,那么只要使其加上f[y][0]即可。不过别忘了f[x][1]要初始化为x的快乐指数。

    读入的时候顺便可以计算一下每个人的入度,入度为零的就是根节点。

    最后的答案就是在f[root][0]与f[root][1]里面取最大值。

    还有一道很像的题:战略游戏。可以稍微做一做,练练手。

    经典例题二:[CTSC1997]选课

    树上背包经典题,和分组背包很像。首先可以用f[x][t]表示在以x为根的基础上学t门课程可以得到的最大学分。然后我们可以用分组背包的思想,在一个子节点的子树上选j门课,剩余的选t-j门课,进行状态转移。

    因为可能有多门课程没有先修课,所以我们需要建一个虚拟节点0来作为所有没有先修课的课程的根,当然由于是虚拟的,加上根的学分时不用考虑虚拟节点。最终答案即为f[0][m]。

    具体实现还得见代码。

    void dp(int x)
    {
    	f[x][0]=0;
    	for(int i=0;i<son[x].size();i++)
    	{
    		int y=son[x][i];
    		dp(y);
    		for(int t=m;t>=0;t--)
    		for(int j=t;j>=0;j--)
    		if(t-j>=0)f[x][t]=max(f[x][t],f[x][t-j]+f[y][j]);
    	}
    	if(x!=0)
    	{
    		for(int t=m;t>0;t--)
    		f[x][t]=f[x][t-1]+s[x];
    	}
    }
    

    习题:有线电视网

    换根DP

    树形DP的变种之一,经典模型如下:

    给你一棵无根树,要你选一个根,使得所有节点的深度之和最大。

    一个最简单的想法就是枚举每一个点为根,分别计算以它为根时的节点深度之和,最后去比较大小。

    但是面对 (10^6) 的数据规模,这个算法肯定会超时,所以我们要另寻他路。

    我们可以先任选一个点作为根,计算出以它为根的所有节点深度之和,并存进f数组。用f[x]来表示以x为根的深度之和。

    看图

    我们先以1号节点为根,然后观察可以发现,当根从1号节点转换到它的子节点:4号节点时,以4号节点为根的子树中的节点深度都减一,而剩余的节点深度都加一。

    我们可以用size[x]代表以x为根的子树里有多少节点,这样我们就可以得到转移的方程式。

    (f[y]=f[x]+n-2*size[y])

    剩余的节点数即为n-size[y],然后再减去size[y],便得到了如上式子。

    size数组可以在一开始求f[1]时就预处理得到,然后我们再通过第二次遍历将所有的f都求出来,最后就可以比较了。

    主要部分代码

    void dp(int x,int fa)
    {
    	size[x]=1;dep[x]=dep[fa]+1;
    	for(int i=head[x];i;i=Next[i])
    	{
    		int y=ver[i];
    		if(y==fa)continue;
    		dp(y,x);
    		size[x]+=size[y];
    	}
    }
    void dfs(int x,int fa)
    {
    	for(int i=head[x];i;i=Next[i])
    	{
    		int y=ver[i];
    		if(y==fa)continue;
    		f[y]=f[x]+n-2*size[y];
    		dfs(y,x);
    	}
    }
    

    通过模型还可以造出一些简单的变式,如加上点权的Tree with Maximum Cost以及同时加上点权和边权的Great Cow Gathering G。这些只需在模板上做一点简单的改动即可。

    状压DP

    全称是状态压缩DP,此类DP的模型是给你一个 (n imes m) 的地图,让你计算在里面放不重合的 (k) 个图形的方案数。

    我的表达能力并不好,所以我们还是先看到状压的入门题:k国王问题

    可以发现,这道题的数据范围非常小,但是状态非常多,用搜索肯定会超时,这时候就要使用状压DP了。其实,如果你看到这么小的数据范围,且用搜索难以解决的题,那多半就是状压DP。

    状态压缩的思想我们在将bfs的时候就已经说过,即把每一种状态缩成一个数字,这样方便我们进行DP。

    状压DP一般使用二进制进行状态压缩,在本题中,我们就可以用一个 (n) 位的二进制数来代表压缩后的状态。若第 (j) 位为1,则代表这里放了一个国王,为0则没有放。

    我们可以用 (f[x][j]) 代表第x行,状态压缩后的数字为j来代表方案数。

    首先要知道,我们不可能存下所有的状态,那样数组也开不下,因此需要预处理出可以选择的状态,从 (0-2^n-1) 的所有状态中,只有每两位1之间都有至少一个0的状态才能被选。

    因为由题意可知,若两个1靠在一起,表示有两个国王会互相攻击,那么这种状态不符合题目要求,肯定不能被选。

    在预处理结束后,还需要判断当前行的状态与上一行的状态是否符合,也就是我们在进行状态转移时,一定要这一行的状态与上一行的状态不会冲突才可以转移。

    注意:状压DP的很多判断都要通过位运算来快速实现,因此一定要用好位运算,并能看懂每种位运算操作所代表的含义

    说这么多,可能还是不如代码有效,那就看代码吧

    模板题AC代码

    #include<iostream>
    #include<cstdio>
    using namespace std;
    long long ans,f[11][151][151];
    int n,k,s0,s[155],num[155];
    int main()
    {
    	cin>>n>>k;
    	for(int i=0;i<(1<<n);i++)
    	{
    		if(i&(i<<1))continue;
    		int cnt=0;
    		for(int j=0;j<n;j++)
    		if(i&(1<<j))cnt++;
    		num[++s0]=cnt;
    		s[s0]=i;
    	}
    	f[0][1][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=s0;j++)
    		for(int kk=0;kk<=k;kk++)
    		if(kk>=num[j])
    		for(int t=1;t<=s0;t++)
    		if(!(s[t]&s[j])&&!(s[j]&(s[t]<<1))&&!(s[j]&(s[t]>>1)))
    		f[i][j][kk]+=f[i-1][t][kk-num[j]];
    	}
    	for(int i=1;i<=s0;i++)ans+=f[n][i][k];
    	printf("%lld",ans);
    	return 0;
    }
    

    习题:炮兵阵地

  • 相关阅读:
    Hadoop1重新格式化HDFS
    centos6.5之Hadoop1.2.1完全分布式部署安装
    Mybatis延迟加载和查询缓存
    剑指offer面试题7:用两个栈实现队列
    Mybatis输入输出映射
    Mybatis开发Dao
    Mybatis入门程序
    SpringMVC系列之URL匹配问题
    SpringMVC系列之主要组件
    mysql学习笔记(三)
  • 原文地址:https://www.cnblogs.com/luotao0034/p/14040251.html
Copyright © 2011-2022 走看看