DP选做
DP选做
P4805 [CCC2016] 合并饭团 [区间DP] [单调性优化] [提高+/省选-]
和普通区间DP有一点点区别,就是多加了一个三个区间的合并
至于普通合并是\(n^4\),再加一个单调性优化即可变成\(n^3\)
即左区间的右端点右移时右区间的左端点一定左移,区间相等后还要看中间区间能不能变成一个饭团
P2238 逛庙会 [普通DP] [状态压缩] [省选/NOI-]
考虑一开始想的是压上下左右四个状态,后面发现只有下和右状态有用
接下来发现做法假了,比如说从上往下转移时,转移到的格子的左边那个格子的状态不见了
后来发现需要记录4个格子
然后写了\(\frac 1 3\)个上午,细节有点多,把代码贴出来吧
check: (int i,int j,int k){
if(get(k,0) && (i == 0 || shop[i-1][j+1] == 0)) return 1;
if(get(k,1) && shop[i][j+1] == 0)return 1;
if(get(k,2) && shop[i+1][j] == 0)return 1;
if(get(k,3) && (j == 0 || shop[i+1][j-1] == 0)) return 1;
return 0;
}
main:
for(int k = 0; k <= 15; ++k)dp[1][1][k] = 0;
for(int i = 1; i <= H; ++i){
for(int j = 1; j <= W; ++j){
if(i == 1 && j == 1)continue;
for(int k = 0; k < 16; ++k){
if(get(k,1) && get(k,2))continue;//下和右不可能都没与处理
if(check(i,j,k)) continue;
int up,down,left,right,now;
balabala...//上向下
for(int l = 0; l < 16; ++l){
if(get(k,0) == 0 && shop[i-1][j+1] != 0)break;//0号点一定没处理过
if(check(i,j-1,l))continue;//检查合法性
if(get(l,2) != get(k,3)) continue;//前面的二号点等于现在的三号点 (继承)
if(get(k,1) + get(k,2) > 1)continue;//没处理的点的个数不能超过1个
left = 0,down = (get(k,2)^1) * shop[i+1][j],right = (get(k,1) ^ 1) * shop[i][j+1];
if((get(k,1) || get(k,2)) && get(l,0)) up = shop[i-1][j];//上面的点如果能不处理就不处理
else up = 0;
now = get(l,1) * shop[i][j];//左面没处理当前点就处理
dp[i][j][k] = min(dp[i][j-1][l] + up + down + left + right + now,dp[i][j][k]);
}//左向右
}
}
}
P2051 [AHOI2009] 中国象棋 [普通DP] [组合数学] [打表] [提高+/省选-]
法一:DP
状态设置:
一开始想到的是部分分十分的显然,就是一个状压DP,但是有没有一种可能,对于前\(~i~\)行这道题只需要知道几列没放,几列放了一个,几列放了两个
\(~dp[i][j][k]~\)表示对于前\(~i~\)行,有\(~j~\)列放了一个,有\(~k~\)列放了两个
法二:打表
考虑把表打完好像交不了,那就打像三角形一样的一半即可,这里就不给出打的表了,有点长……
P3177 [HAOI2015] 树上染色 [树型DP] [上下界优化] [提高+/省选-]
计算每一条边的贡献,就是(下面的黑点数×上面的黑点数+下面的白点数×上面的白点数) ×边的边权
考虑需要进行上下界优化
void dfs(int u,int fa){
siz[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to,val = edge[i].val;
if(v == fa) continue;
dfs(v,u);
siz[u] += siz[v];
for(int j = min(siz[u],m); j >= 0; --j)
for(int k = max(0,j - (siz[u] - siz[v])); k <= min(siz[v],j); ++k){
if(dp[u][j-k] == -1) continue;
ll tot = val * ((ll) k * (m - k) + ((ll)siz[v] - k) * (n - m - siz[v] + k));
dp[u][j] = max(dp[u][j],dp[u][j - k] + dp[v][k] + tot);
}
}
}
本质上就是说对\(~j~\)的枚举进行一个剪枝
[POI2017]Sabotaż [树状DP] [提高+/省选-]
solution 1:二分答案
考虑每次二分一个答案,然后进去去跑一遍看他能不能跑出\(~k~\)个间谍,理论上\(~O(nlog_n)~\)不知道能不能过
solution 2:树状DP
考虑设计状态,令\(~dp[i]~\)表示让\(~i~\)以及它的子树不全部叛变的最小的\(~x~\),即全部叛变的最大的\(~x~\),定义\(~siz[i]~\)为以\(~i~\)为子树的点的个数
特别地,叶子节点的\(~dp~\)值为\(~1.00~\)
解释:
- 一个子树让它的父亲叛变的条件需要都满足(子树叛变,并且\(~x~\)要小于该子树所占的比例),所以取\(~min~\)
- 全部叛变最大的\(~x~\),所以取\(~max~\)
- \(~ans~\)取\(~max~\)同理
P3195 [HNOI2008] 玩具装箱 [普通DP] [斜率优化] [省选/NOI-]
斜率优化板子
P2120 [ZJOI2007] 仓库建设 [普通DP] [斜率优化] [省选/NOI-]
斜率优化,然后有一些点需要注意
- 在末尾可能有\(~p_i=0~\)的无效工厂,这些工厂不需要在\(~n~\)点额外建造一个仓库;
- 有可能全部工厂都是无效工厂,注意特判边界;
- 斜率优化有条件的请使用叉积,如果用除法请注意分母为\(~0~\)的情况;
long double
能够帮助你解决一部分掉精度的问题,当然本题不一定需要;
P2490 [SDOI2011]黑白棋 [博弈论] [计数DP] [组合数] [省选/NOI-]
K-nim游戏 即给你\(~n~\)堆石子,你一次可以取最多\(~K~\)堆,最后取的人获胜
我们将所有数进行二进制拆分,每一位上的和为\(r_i\),如果\(\exist i\in \Z,~r_i\mod k+1~ \not = 0\)那么先手必胜;否则先手必败
prove:
如果所有的\(~r_i~\)都为\(~0~\),则一次操作肯定会使得某些数位上的\(~r~\)值改变。又因为一堆石子每位上都是\(~0~\)或\(~1~\),所以变动的绝对值不会超过\(~k~\)。因此操作结束后该位置上的\(~r_i\mod {(k+1)}~\)必不能为\(~0~\)。
否则,另一名玩家一定可以通过一次操作将所有的\(~r_i~\)变成\(~0~\)
这道题,不一样的是,最后取的为失败,那么我们就设\(~dp[i][j]~\)表示前\(~1\to n~\)位的\(~r~\)都是\(~0~\),现在剩了\(~j~\)个石子的方案数
然后该怎么推怎么推啦!(不想写了逃)
P2605 [ZJOI2010]基站选址 [普通DP] [线段树优化] [NOI/NOI+/CTSC]
P6563 [SBCOI2020] 一直在你身旁 [区间DP] [单调队列优化] [省选/NOI-]
如果我有人一直在我身旁就好了(酸
考虑一眼区间DP
设计状态\(~dp[i][j]~\)表示将电线的长度范围缩小到区间\(~[i,j]~\)的最小代价
那么我们可以知道\(~O(n^3)~\)做法:
\(dp[i][j] = min(dp[i][j],max(dp[i][k],dp[k+1][j])+a[k])\)
看数据范围,好像是\(O(n^2)\)
这个区间的枚举显然是不怎么变的,如果要变的话也只能是把\(~k~\)的转移进行一个优化
如果说把\(~max()~\)这个可恶的东西去掉,那么就显然是一个单调队列优化
如果只是从\(~dp[i][k]+a[k]~\)转移的话,那么就是单调递增的,取最左边的即可
如果只是从\(~dp[k+1][j]+a[k]~\)转移的话,那么就是一个单调队列即可
加上\(~max()~\)就是进行一个分类讨论
那么当\(~dp[i][k]>dp[k+1][r]~\)时从\(~dp[i][k]+a[k]~\)转移,随着\(~r~\)指针右移,\(~k~\)指针左移
那么当\(~dp[i][k]<dp[k+1][r]~\)时从\(~dp[i+1][j]+a[k]~\)转移,随着\(~r~\)指针右移,向单调队列中往右边加入值,单调队列从左至右单减
这样我们的循环也要变一下,先\(~r~\)再\(~l~\)
P1131 [ZJOI2007] 时态同步 [数型DP?] [贪心] [提高+/省选-]
考虑不像DP,更像贪心
你知道,如果说在不改变到根节点距离最长的子节点的长度的情况下,改变的边的长度越深,方案越优
then,就贪心完了呗
void dfs(int u,int fa){
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
dfs(v,u);
dis[u] = max(dis[u],dis[v] + edge[i].val);
}
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
ans += dis[u] - dis[v] - edge[i].val;
}
}
\(~dis[u]~\)表示\(~u~\)的子树中,最长的子树中节点到\(~u~\)的距离
P2466 [SDOI2008] Sue 的小球 [区间DP] [预处理当前选择对将来选择的影响] [省选/NOI-]
这道题,一看就是一个区间DP,然后呢……(LXW停止运行,正在向HF报告错误
考虑,你显然要先排一遍序
然后可以设计状态:
考虑得分这个东西好像不太好搞,那么我们可以把最大得分改为最小失分
then,你可以发现一些奇妙的发现:
你并不能确定\(~dp[i][j][k]~\)的时间是多少,那么你的失去的分数就不太好算,这时候就到了这到题的精华
预前处理当前选择对将来选择的影响!
比如说,你从\(~dp[i+1][j][0]~\)转移到\(~dp[i][j][0]~\):
- 在你移动的这段时间里,\(~1\sim i~\)和\(~j+1 \sim n~\)的所有彩蛋都会减少一些分数,这些分数即是转移时需要加上的值
最后是一段核心转移code:
为了写起来方便,把\(~f[i][j][0]~\)变成了\(~f[i][j]~\),把\(~f[i][j][1]~\)变成了\(~g[i][j]~\)
\(~pre[i]~\)为\(~1\sim i~\)的\(~v~\)的和
望知晓!!
f[i][j] = min(f[i][j],f[i+1][j] + (pre[i] - pre[0] + pre[n] - pre[j]) * (ball[i+1].x - ball[i].x));
f[i][j] = min(f[i][j],g[i+1][j] + (pre[i] - pre[0] + pre[n] - pre[j]) * (ball[j].x - ball[i].x));
g[i][j] = min(g[i][j],g[i][j-1] + (pre[i-1] - pre[0] + pre[n] - pre[j-1]) * (ball[j].x - ball[j-1].x));
g[i][j] = min(g[i][j],f[i][j-1] + (pre[i-1] - pre[0] + pre[n] - pre[j-1]) * (ball[j].x - ball[i].x));
P2607 [ZJOI2008] 骑士 [树型DP] [基环树] [省选/NOI-]
就是上司的舞会(树型DP),然后多了你要有很多判环,反正是一道基环树DP板子,看看题解即可。
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/17237097.html