这次训练总结是11月24日-11月26日。
树形DP专题结束了。。。
嗯,做的太快,现在都快忘了。。。幸好老师正好留出一天时间让我们复习。赶紧回顾一下数位DP专题和树形DP专题。
现总结一下这三天的情况:简单预习了状压DP的知识,看了一部分资料。思想倒是不难理解,就是利用二进制01表示状态,用&|来判断状态进行DP。但是一看题目和代码,一脸懵逼,还需要时间来揣摩思考。
cf打了一场比赛,题目比较水,都是思维题,不到两小时出了4题。最后一题是并查集,也不是很难,但是没时间做了(补)。找回了一点自信。
接下再来复习一下数位DP和树形DP:
数位DP:
数位DP,就是对每一位进行DP。。。有两种,一种是直接用状态转移方程按位DP,这种写法我不太懂。另一种是用DFS记忆化来完成状态转移,对每一位进行DP。我对第二种写法比较熟悉。
题型:主要是查找某一区间内满足条件的数的个数或者第几个满足条件的数,n会给的比较大。满足的条件千奇百怪,有不要62(62不能出现在一个数里),有求奇数项偶数个偶数项奇数个(HDU 5898),有求奇数出现了偶数次偶数出现奇数次(SPOJ BALNUM),有求回文数,山峰数,数字计数,回文、镜像回文、每一位数能整除原数、每一位数的和能整除原数、周期数、相邻数字差大于小于m、区间里第几个满足条件的数等等。。。当然这些只是我做过的题目,还有很多题目没做过,以后回来提高的时候再多做。
需要注意的是:注意状态转移要正确,这样是否满足条件,参数都要记录哪些条件,是否有上下界,初始化是否可以放在外面,有没有要特判的情况,long long还是int,dp数组开几维能保证所求满足条件的数正确。对于每一位数和是否能整除原数这类问题,只要枚举每一位数字的和即可,因为最多为位数*9,求区间里第几个满足条件的数再套个二分就可以。以后还需要多练习来强化记忆。
模板(DFS记忆化写法):
int dfs(int pos,int num,bool limit) {//dfs(位置,要记录的条件(比如不要62,那这一位记录前一位是否为6),是否到临界值),有的题目还要加个参数判断前导0
if(pos==-1) {
return num==m;//枚举完了所有数判断是否符合条件,这里是判断所有位的和是不是等于m
}
if(!limit&&f[pos][num]!=-1) return f[pos][num];//未到临界值并且记忆过,则直接返回记忆的值,这就是记忆化的好处,省时间
int i,end=limit?a[pos]:(b-1);//到临界值即为给定数字,否则为对应的进制位数-1,这里为b进制
int ans=0;//记录从这一位开始往下找到的满足条件的数
for(i=0;i<=end;i++) {//枚举这一位可能的数
ans+=dfs(pos-1,num+i,limit&&i==end);//把满足条件的数加起来
}
if(!limit) f[pos][num]=ans;//未到临界值则记忆化
return ans;//返回满足条件的数的总数
}
int solve(int xx)//如果满足a~b=1~b-1~a-1,则结果为solve(b)-solve(a-1),不满足则就为有上下界的数位DP,如HDU 3565 求区间内两座山峰的数的个数
{
int pos=0;
while(xx)
{
a[pos++]=xx%b;//将xx转化为b进制存到a数组里,然后就可以按每一位进行查找
xx/=b;
}
return dfs(pos-1,0,1);//求出1~xx区间里满足条件的数的个数,包括xx。(这里是求区间里满足每一位数的和等于m的数的个数)
}
树形DP:
树形DP,就是把DP放树上做。。。方向有上到下和下到上,细节比较多,初始化也要注意,最难的还是状态转移方程。
题型:做的专题里面有好几道是树形背包(费用分配问题,好多题目。。。),除此之外的题型还有删点删边问题(例如 poj3140 给出一棵树,求去掉一条边后的两棵子树节点权值的和的差的最小值。),求树上结点最远距离(HDU2196),重心(重心就是删除一个节点之后,剩下的子树中节点数量最多的最少)(poj 1655 3107),有限步数取得最大值(注意可以往回走,poj2486),建城市最小费用最大价值等问题(HDU4044),题目类型都与树有关。做的题还是比较少,理解的不够透彻。
注意:建图是否为有向图,遍历是从下到上还是从上到下。细节非常多,初始化一定要注意。状态转移方程难想。
比较喜欢的一道题目就是poj 3345 Bribing FIPA,经典的背包+map+特别输入,感觉学会了很好用的输入。题解已经写了。
模板:树形背包
void add(int z,int f)//建树,注意初始化h为-1,t为1
{
a[t].v=z;
a[t].nex=h[f];
h[f]=t++;
}
void dfs1(int u)//有的题目既需要从上往下遍历又需要从下往上遍历,就用两个dfs
{
sum[u]=1;//这个节点的初始节点数为1
dp[u][0]=0;//0个节点费用为0
int j,k;
for(int i=h[u];i!=-1;i=a[i].nex)//遍历这颗子树
{
int z=a[i].v;
dfs1(z);
sum[u]+=sum[z];//已经遍历了这颗子树的sum[u]个节点
for(j=sum[u];j>=1;j--){//这里就是最难的状态转移方程...
for(k=0;k<=j&&k<=sum[z];k++) dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[z][k]);//状态转移,访问u树的j-k个点,k个z子树的点所取得的最小花费。
}
}
dp[u][sum[u]]=c[u];//这里的模板是Bribing FIPA 的,关键就是访问了这颗子树的所有点之后,费用仅为c[u]
}
虽然做了19道,不过感觉还是做的太少,以后有待进一步巩固提高。
接下来就是状压DP专题,还有我的AC自动机,已经都开始看资料了。(主要先看状压DP,据说不简单,AC自动机在已经看完字典树和KMP的基础上应该能好理解一些吧)
继续拼!
做过的内容要消化、吸收,不吃夹生饭,不为完成任务而刷题!
做过的内容要消化、吸收,不吃夹生饭,不为完成任务而刷题!
做过的内容要消化、吸收,不吃夹生饭,不为完成任务而刷题!