动态规划
例1
# include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10, maxk = 110, mod = 1e9 + 7;
void judge() {
freopen("action.in", "r", stdin);
freopen("action.out", "w", stdout);
return;
}
int n, k, siz[maxn], q[maxn], par[maxn], fnt, rar, ans;
int dp[maxn][maxk][2][2], pd[maxk][2][2];
vector<int> g[maxn];
inline void Add(int &x, int y) {
x = x + y < mod ? x + y : x + y - mod;
return;
}
int main() {
judge();
scanf("%d%d", &n, &k);
for (int i = 1; i < n; ++i) {
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
q[rar++] = 1;
while(fnt != rar) {
int u = q[fnt++];
siz[u] = 1;
dp[u][0][0][0] = dp[u][1][1][0] = 1;
for (int i = 0; i < g[u].size(); ++i) {
int &v = g[u][i];
if(v != par[u]) {
par[q[rar++] = v] = u;
}
}
}
for (int ti = rar - 1; ti; --ti) {
int &u = q[ti], &p = par[u], Endu = min(siz[u], k), Endp = min(siz[p], k);
for (int ip = 0; ip <= Endp; ++ip) {
for (int xp = 0; xp < 2; ++xp) {
for (int yp = 0; yp < 2; ++yp) {
pd[ip][xp][yp] = dp[p][ip][xp][yp];
dp[p][ip][xp][yp] = 0;
}
}
}
for (int ip = 0; ip <= Endp; ++ip) {
for (int xp = 0; xp < 2; ++xp) {
for (int yp = 0; yp < 2; ++yp) {
static int s;
if(s = pd[ip][xp][yp]) {
int End = min(Endu, k - ip);
for (int iu = 0; iu <= End; ++iu) {
for (int xu = 0; xu < 2; ++xu) {
for (int yu = xp ^ 1; yu < 2; ++yu) {
Add(dp[p][iu + ip][xp][yp | xu], (long long) s * dp[u][iu][xu][yu] % mod);
}
}
}
}
}
}
}
siz[p] += siz[u];
}
for (int xu = 0; xu < 2; ++xu) {
Add(ans, dp[1][k][xu][1]);
}
printf("%d\n", (ans + mod) % mod);
return 0;
}
数位DP
定义 \(S(n)\) 为将 \(n\) 在 \(10\) 进制下的所有数位从小到大排序后得到的数。例如:\(S(1)\ =\ 1,\ S(50394)\ =\ 3459,\ S(323)\ =\ 233\)。给定 \(X\) 求 \(\sum_{i\ =\ 1}^X\ S(i)\) 对 \(10^9\ +\ 7\) 取模的结果。
数据范围:\(1\ \leq\ X\ \leq\ 10^{700}\)。
sol:
考虑如何计算答案,可以通过分别计算每一位对总和的贡献来求。定义 \(cnt(i,\ x)\) 为 \(S(1),\ S(2),\ ...,\ S(X)\) 中第 \(i\)位为 \(x\) 的数量。
直接求 \(cnt(i,\ x)\) 仍然较为困难,考虑差分。令 \(dlt(i,\ x)\) 为第 \(i\) 位上有多少个数 \(\geq\ x\)。对于一个 \(S(y)\) 中第 \(i\) 位 \(\geq\ x\) 的数 \(y\),可以发现其必须满足有至少 \(i\) 个数位 \(\geq\ x\),这样就可以进行dp了。
另 \(dp(i,\ j,\ x,\ cmp)\) 表示填了前 \(i\) 位,有至少 \(j\) 个数字 \(\geq\ x\),与 \(N\) 的大小关系位 \(cmp\)。
转移时直接枚举第 \(i\ +\ 1\) 位数字是多少即可。
枚举子集
S = 1 << n;
for (int s = 0; s < S; ++s) {
for (int t = s; ; t = (t - 1 & s)) {
...
if(t == 0) break;
}
}
x & (-x)
s = 1101100110
t = 1101100110
t - 1= 1101100101
(t - 1) & s = 1101100100
状压DP
你准备去 \(N\) 个国家进行旅行,去第 \(i\) 个国家的旅行会在第 \(s_i\) 天的早上出发,第 \(s_i\ +\ len_i\ -\ 1\)天的晚上回家。
你有 \(P\) 本护照,在每次旅行前必须让其中一本护照办理该国的签证,如果在第 \(x\) 天开始对某本护照办理第 \(i\) 个国家的签证,那么第 \(x\) 天不能在旅行,且第 \(x\ +\ t_i\) 天中午可完成签证拿回该护照(允许在旅行时拿到)。判断旅行计划能否完成,如果能,给出一种签证方案(时间及哪本护照)。
数据范围:\(1\ \leq\ N\ \leq\ 22,\ 1\ \leq\ P\ \leq\ 2\)。
NOIP2014 飞扬的小鸟
Flappy Bird是一款风靡一时的休闲手机游戏。为了简化问题,我们对游戏规则进行了简化和改编:
游戏界面是一个长为 \(n\),高为 \(m\) 的二维平面,其中有 \(k\) 个管道(忽略管道的宽度)。 小鸟始终在游戏界面内移动。小鸟从游戏界面最左边任意整数高度位置出发,到达游戏界面最右边时,游戏完成。 小鸟每个单位时间沿横坐标方向右移的距离为 \(1\),竖直移动的距离由玩家控制。如果点击屏幕,小鸟就会上升一定高度 \(X\),每个单位时间可以点击多次,效果叠加;如果不点击屏幕,小鸟就会下降一定高度 \(Y\)。小鸟位于横坐标方向不同位置时,上升的高度 \(X\) 和下降的高度 \(Y\) 可能互不相同。 小鸟高度等于 \(0\) 或者小鸟碰到管道时,游戏失败。小鸟高度为 \(m\) 时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟最多可以通过多少个管道缝隙。
NOIP2017 宝藏
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋,也给出了这 \(n\) 个宝藏屋之间可供开发的 \(m\) 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度 \(x\) 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
CF506A
有 \(30000\) 个岛屿从左到右排列,有 \(n\) 个宝石,在 \(p_1,\ p_2,\ ...,\ p_n\)上。 你初始时在 \(0\) 号岛上,第一次跳到 \(d\) 号岛上,第 \(i\ (i\ >\ 2)\) 次你向右跳跃的距离为第 \(i\ -\ 1\) 次跳跃距离 \(l\) 或者 \(l\ -\ 1\) 或者 \(l\ +\ 1\) 。问最多能拿多少宝石。
数据范围:\(1\ \leq\ n,\ d\ \leq\ 30000\)。
ARC081F
\(w × h\) 的矩阵,每个格子为黑色或者白色。你可以进行任意次操作,每次操作将某一行或者某一列颜色取反。问可以得到的最大全黑子矩形的面积。
数据范围: \(2\ \leq\ w,\ h\ \leq\ 2000\)。
浙公网安备 33010602011771号