树形DP 保卫王国P5024

前置知识

1、邻接表 + Dfs(深度优先搜索)

2、基础DP(如 01背包 )

3、最小公共祖先(LCA)

首先解读一下题意

城市即为节点

每个节点都有一个驻军资金 即节点的权值

现在要让每两个节点之间至少有一个节点拥有驻军

并给出 m 个要求

求出每个要求所对应的最小花费

为了更好的阅读体验

  • 三个方法的完整代码将放在文章最后
  • 请先大致浏览完完整代码再阅读解释
  • 请配合草稿本画图并记录变量名含义

无优化 44分

解题方法

树形DP

顾名思义 是在树形结构中使用动态规划

DP是一种暴力算法

因此把每个节点取与不取的子树最小权值都算出来

就可在根上算出答案

算法评估

由于有多个要求

因此有几个要求 dfs就会执行几次

这导致运行时间大大变长

由于44分代码比较简单 所以就直接把注释打入代码了

部分优化 68分

解题方法

现在我们要想办法解决重复执行的问题

我们可以通过一个预处理来把他们之间之外的算出来

这样就可以大大提升运算速度

下半部分的红色我们已经搞定了

就和无优化部分的一样 处理出 \(f[i][0/1]\) 即可

而对于 \(ff[i][0/1]\) 就要用从上往下的Dfs来解决了 具体看代码

求ff

void dfs2(int u,int fa){
for(int i = head[u];i;i = e[i].u){
int ev = e[i].v;
if(ev == fa) continue; ff[ev][0] = ff[u][1] + f[u][1] - min(f[ev][0],f[ev][1]);
ff[ev][1] = min(ff[u][0] + f[u][0] - f[ev][1],ff[ev][0]);
dfs2(ev,u);
}
}

主要来看看这两个状态转移方程

\(ff[ev][0]\) :既然ev为0(不可取) 则u必须为1(可取)

\(f[ev][1]\) :ev为0(取) 则u为0/1皆可

所以求出\(ff[ev][0/1]\)通过以上两个方程即可

可以看到Dfs2的调用被放在了状态转移方程的下面

因为这个Dfs是自上而下的 所以在上面处理完后再进行下面的调用

关于trans函数

void trans(int id){
int a = rest[id].node1;
int b = rest[id].node2;
int x = rest[id].val1;
int y = rest[id].val2;
memset(dp,sizeof(dp),0); if(x == 0){
dp[a][0] = f[a][0];
dp[a][1] = INF;
} else {
dp[a][1] = f[a][1];
dp[a][0] = INF;
}
if(y == 0){
dp[b][0] = f[b][0];
dp[b][1] = INF;
} else {
dp[b][1] = f[b][1];
dp[b][0] = INF;
}
}

在无优化中 trans函数是编辑了vis数组

而在部分优化中 trans函数直接将对应的dp值设置为最大值-INF

这样在min中的挑选下,肯定能将设为INF的状态过滤掉

关于LCAans函数

ll LCAans(int x,int y){
int t;
if(deep[x] < deep[y]) swap(x,y);//调换x为跟深的节点
while (deep[x] > deep[y] && father[x] != y){//调节至同一深度&&x不为y
t = father[x];
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1];
x = t;
}
if(father[x] == y){//若x为y的儿子
dp[y][1] = dp[y][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[y][0] = dp[y][0] - f[x][1] + dp[x][1];
return min(dp[y][1] + ff[y][1],dp[y][0] + ff[y][0]);
} while(father[x] != father[y]){//共同向上
t = father[x];
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1];
x = t;
t = father[y];
dp[t][1] = f[t][1] - min(f[y][0],f[y][1]) + min(dp[y][0],dp[y][1]);
dp[t][0] = f[t][0] - f[y][1] + dp[y][1];
y = t;
}
t = father[x];//处理LCA节点时等于将两边都砍掉重新接
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1])
- min(f[y][0],f[y][1]) + min(dp[y][0],dp[y][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1] - f[y][1] + dp[y][1]; return min(dp[t][0] + ff[t][0],dp[t][1] + ff[t][1]);
}

这是LCA非倍增的写法

遍历全代码 可以发现基本上都是重复的:

	t = father[x/y];
dp[t][1] = f[t][1] - min(f[x/y][0],f[x/y][1]) + min(dp[x/y][0],dp[x/y][1]);
dp[t][0] = f[t][0] - f[x/y][1] + dp[x/y][1];

理解性来看

就是把t节点的下半部分置换成更新好的下半部分

然后作为其dp值

还有一点要强调

就是若x为y的儿子那块

被砍腿的是dp数组而不是f数组

因为y也是有要求的

但是f[y]没有被INF给标记过

因此要用被标记过的dp来砍

main()函数中要注意的几点

//只截取了for循环的部分
trans(i);
ll res = LCAans(rest[i].node1,rest[i].node2);
if(res < INF) cout << res << endl;
else cout << "-1" << endl;
  • 记得调用trans(i)

    • 每轮都要预处理
  • LCAans的代入值是node1,node2

    • 因为LCA从要修改的节点开展
  • 要加入res与INF的比较

    • 如果res>INF则说明无法同时满足两种情况

算法评估

通过两次Dfs求出了 \(f\) 和 \(ff\) 数组

将上下两端的计算量大大减少

明显提升的算法效率

但是遇到中间部分比较长的情况

就需要用到大量的时间来反复处理

既然是LCA 那自然可以想到倍增的算法

接下来就进行倍增的处理

百分优化

解题方法

100分代码与68分代码大致相同

共有以下变化

  • father增加了对次方祖先的表达
  • 用两个一维小数组来代替dp数组
  • 添加了LCA函数

father数组

之前的father数组只用于表达节点的亲生父亲

而现在表达的是 u 的 \(2^t\) 辈祖先

因此dfs1中的father的表达也要有所改变:

	father[u][0] = fa;

\(2^0 = 1\) 即为 u 的亲生父亲

LCA函数

其主要作用是计算出fa数组 ———用于倍增是树段的计算

fa数组就是这个优化的核心部分

fa数组代表的含义为

u节点的 \(2^t\) 辈祖先为根的子树 减去 u节点为根的子树

所对应的最小权值

第一个0(不取)/1(取)对应u

第二个0(不取)/1(取)对应u的\(2^t\)辈祖先

这个fa数组是用递推求出的:

void LCA(){
for(int i = 1;i <= n;i++){
int t = father[i][0];
fa[i][0][0][0] = INF;
fa[i][0][0][1] = f[t][1] - min(f[i][0],f[i][1]);
fa[i][0][1][0] = f[t][0] - f[i][1];
fa[i][0][1][1] = fa[i][0][0][1];
}
for(int t = 1;t <= 20;t++){
for(int i = 1;i <= n;i++){
int X = father[i][t-1];
father[i][t] = father[X][t-1];
fa[i][t][0][0] = min(fa[i][t-1][0][0] + fa[X][t-1][0][0],
fa[i][t-1][0][1] + fa[X][t-1][1][0]);
fa[i][t][0][1] = min(fa[i][t-1][0][0] + fa[X][t-1][0][1],
fa[i][t-1][0][1] + fa[X][t-1][1][1]);
fa[i][t][1][0] = min(fa[i][t-1][1][0] + fa[X][t-1][0][0],
fa[i][t-1][1][1] + fa[X][t-1][1][0]);
fa[i][t][1][1] = min(fa[i][t-1][1][0] + fa[X][t-1][0][1],
fa[i][t-1][1][1] + fa[X][t-1][1][1]);
}
}
}
第一个for循环

这是递推的基础

定义 t 为i的亲生父亲

fa[i][0][0][0]:

i和t都为0(不取) 不符合题意 因此取INF

fa[i][0][0][1]:

i不取t取 因为t是取的 所以f[i]是要取min的

fa[i][0][1][0]:

i取t不取 因为t是不取 所以f[i]只能为1

fa[i][0][1][1]:

i取t取 因为都是自由的 所以和 fa[i][0][0][1] 相同

第二个for循环

原理:

这里用了一个中将量 X

处理 X 的 0/1 状态

因为 fa 是已经处理好的最小权值

所以直接简单相加就可以了

可行性:

t 代表 i 的 \(2^t\) 辈祖先

由于循环外层是 t 内层为 i

说明 t - 1 层已经循环处理完毕了

因此用 X 这个中间量是可以的

father数组:

利用 X 这个中间值可递推出father

可行性与上文相同

LCAans函数

ll LCAans(int x,int y,int val1,int val2){
ll tx[2] = {0,0};
ll ty[2] = {0,0}; dpx[0] = dpx[1] = INF;
dpy[0] = dpy[1] = INF; if(deep[x] < deep[y]){
swap(x,y);
swap(val1,val2);
} dpx[val1] = f[x][val1];
dpy[val2] = f[y][val2];

我们为了实现0/1的要求

我们先把 dpx 和 dpy 全部赋值成 INF

然后再将可通行的赋值为正常值

    for(int t = 20;t >= 0;t--){
if((deep[x] - deep[y]) >= (1<<t)){
tx[0] = min(fa[x][t][0][0] + dpx[0],
fa[x][t][1][0] + dpx[1]);
tx[1] = min(fa[x][t][0][1] + dpx[0],
fa[x][t][1][1] + dpx[1]);
dpx[0] = tx[0];
dpx[1] = tx[1];
x = father[x][t];
}
} if(x == y) return dpx[val2] + ff[x][val2];

这里将 x 和 y 移动到同一层

执行条件是 x 和 y 之间的距离大于 \(2^t\)

这保证了 x 不会跳过头

然后就是嫁接的部分了

这里的 tx 是 x 的 \(2^t\) 辈祖先

最后要判断 x 是否和 y 重合

因为如果重合了

y 是不能随意取的

需要满足 val2 的要求

    for(int t = 20;t >= 0;t--){
if(father[x][t] != father[y][t]){
tx[0] = min(fa[x][t][0][0] + dpx[0],
fa[x][t][1][0] + dpx[1]);
tx[1] = min(fa[x][t][0][1] + dpx[0],
fa[x][t][1][1] + dpx[1]);
dpx[0] = tx[0];
dpx[1] = tx[1];
x = father[x][t]; ty[0] = min(fa[y][t][0][0] + dpy[0],
fa[y][t][1][0] + dpy[1]);
ty[1] = min(fa[y][t][0][1] + dpy[0],
fa[y][t][1][1] + dpy[1]);
dpy[0] = ty[0];
dpy[1] = ty[1];
y = father[y][t];
}
}

执行条件是他们的 \(2^t\) 辈祖先不是同一个

这让循环结束后 x 和 y 在他们的 LCA 下方

这里的嫁接和上文同理 不再赘叙

    int t = father[x][0];
ll lca_0 = f[t][0] - f[x][1] - f[y][1] + dpx[1] + dpy[1] + ff[t][0];
ll lca_1 = f[t][1] - min(f[x][0],f[x][1]) - min(f[y][0],f[y][1])
+ min(dpx[0],dpx[1]) + min(dpy[0],dpy[1]) + ff[t][1];
return min(lca_0,lca_1);
}

这里是处理返回值 和上文相同

代码

44分

//P5024 44p 注释版
#include<bits/stdc++.h>
#define ll long long
#define maxn 100005
using namespace std; int n,m,val[maxn],vis[maxn];
char type[100];
ll f[maxn][2];
/*
n 城市数量(即节点数) m 要求数量
val[i] 城市i所部署军队的耗资(即节点i的权值)
vis[i] 要求的存储变量 vis[i] = 0(代表城市i不得驻军 即节点i不可取) 1(代表城市i必须驻军 即节点i必取)
type[] 用来存储规模参数
f[i][j] i代表节点 j = 0(代表不取当前节点的最小子树权值和) 1(代表取当前节点的最小子树权值和)
*/ int cnt,head[maxn];
struct tree{
int u,v;
tree(int a = 0,int b = 0){
v = a;
u = head[b];
}
}e[maxn<<1];
//邻接表存图 struct Rest{
int node1,node2;
int val1,val2;
}rest[maxn];/*
用结构体来存储要求
node1/2存要求的节点
val1/2存必取或必不取
*/ void Read(){//读入
int a,b;
cin >> n >> m >> type;
for(int i = 1;i <= n;i++) cin >> val[i]; //存节点权值
for(int i = 1;i < n;i++){//存图
cin >> a >> b;
e[++cnt] = tree(a,b);
head[b] = cnt;
e[++cnt] = tree(b,a);
head[a] = cnt;
}
int x,y;
for(ll i = 1;i <= m;i++){//读入要求
cin >> a >> x >> b >> y;
rest[i].node1 = a;
rest[i].val1 = x;
rest[i].node2 = b;
rest[i].val2 = y;
}
} void trans(int id){//在dfs前调用 预处理要求
memset(vis,2,sizeof(vis));
vis[rest[id].node1] = rest[id].val1;
vis[rest[id].node2] = rest[id].val2;
} ll dfs(int u,int fa){//深搜 树形dp
if((vis[u]==0)&&(vis[fa]==0)) return -1;
//排除不可能的情况 即父节点和子节点都取不得 不符合题意 f[u][0] = 0;//父节点不选的时候
f[u][1] = val[u];//父节点选的时候 for(int i = head[u];i;i = e[i].u){//搜每一个父向子的边
int ev = e[i].v;//ev 即子节点
if(ev == fa) continue;//防止向上爬
if(dfs(ev,u) < 0) return -1;//继续往下搜 如果不满足题意则直接返回-1 f[u][0] += f[ev][1];/*
当父节点不选的时候
子节点都必须选择
所以直接累加子节点选择时的权值
*/ if(vis[ev] == 0){
//子节点不能选 则父节点必须选
f[u][1] += f[ev][0];
vis[u] = 1;//修改父节点为必选
} else if (vis[ev] == 1) {
//子节点必选 则只累加子节点必选的最小子树值
f[u][1] += f[ev][1];
} else {
//没有限制 则择优选择小的上报
f[u][1] = min(f[u][1] + f[ev][1],f[u][1] + f[ev][0]);
}/*
当父节点选的时候
子节点可以选或不选
*/ } if(vis[u] == 1) return f[u][1];
if(vis[u] == 0) return f[u][0];
return min(f[u][1],f[u][0]);/*
通过条件决定返回值
用于输出结果
*/
}
int main(){
Read();//读入
for(int i = 1;i <= m;i++){//循环每个条件
trans(i);//读入条件 预处理
ll ans = dfs(1,0);//深搜求答案
cout << ans <<endl;//输出
}
return 0;
}

68分

#include<bits/stdc++.h>
#define ll long long
#define maxn 100005
#define INF 1e12
using namespace std; int n,m;
int head[maxn],cnt;
int val[maxn],deep[maxn],father[maxn];
char type[100];
ll f[maxn][2],ff[maxn][2],dp[maxn][2]; struct Edge{
int u,v;
Edge(int a = 0,int b = 0){
u = head[a];
v = b;
}
}e[maxn<<1]; struct Rest{
int node1,node2;
int val1,val2;
}rest[maxn]; void Read(){
int a,b;
cin >> n >> m >> type;
for(int i = 1;i <= n;i++){
cin >> val[i];
}
for(int i = 1;i <= (n-1);i++){
cin >> a >> b;
e[++cnt] = Edge(a,b);
head[a] = cnt;
e[++cnt] = Edge(b,a);
head[b] = cnt;
}
for(int i = 1;i <= m;i++){
cin >> rest[i].node1 >> rest[i].val1;
cin >> rest[i].node2 >> rest[i].val2;
}
} void dfs1(int u,int fa){
f[u][0] = 0;
f[u][1] = val[u]; father[u] = fa;
deep[u] = deep[fa] + 1; for(int i = head[u];i;i = e[i].u){
int ev = e[i].v;
if(ev == fa) continue;
dfs1(ev,u);
f[u][0] += f[ev][1];
f[u][1] += min(f[ev][0],f[ev][1]);
}
} void dfs2(int u,int fa){
for(int i = head[u];i;i = e[i].u){
int ev = e[i].v;
if(ev == fa) continue; ff[ev][0] = ff[u][1] + f[u][1] - min(f[ev][0],f[ev][1]);
ff[ev][1] = min(ff[u][0] + f[u][0] - f[ev][1],ff[ev][0]);
dfs2(ev,u);
}
} void trans(int id){
int a = rest[id].node1;
int b = rest[id].node2;
int x = rest[id].val1;
int y = rest[id].val2;
memset(dp,sizeof(dp),0); if(x == 0){
dp[a][0] = f[a][0];
dp[a][1] = INF;
} else {
dp[a][1] = f[a][1];
dp[a][0] = INF;
}
if(y == 0){
dp[b][0] = f[b][0];
dp[b][1] = INF;
} else {
dp[b][1] = f[b][1];
dp[b][0] = INF;
}
} ll LCAans(int x,int y){
int t;
if(deep[x] < deep[y]) swap(x,y);
while (deep[x] > deep[y] && father[x] != y){
t = father[x];
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1];
x = t;
}
if(father[x] == y){
dp[y][1] = dp[y][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[y][0] = dp[y][0] - f[x][1] + dp[x][1];
return min(dp[y][1] + ff[y][1],dp[y][0] + ff[y][0]);
} while(father[x] != father[y]){
t = father[x];
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1];
x = t;
t = father[y];
dp[t][1] = f[t][1] - min(f[y][0],f[y][1]) + min(dp[y][0],dp[y][1]);
dp[t][0] = f[t][0] - f[y][1] + dp[y][1];
y = t;
}
t = father[x];
dp[t][1] = f[t][1] - min(f[x][0],f[x][1]) + min(dp[x][0],dp[x][1])
- min(f[y][0],f[y][1]) + min(dp[y][0],dp[y][1]);
dp[t][0] = f[t][0] - f[x][1] + dp[x][1] - f[y][1] + dp[y][1]; return min(dp[t][0] + ff[t][0],dp[t][1] + ff[t][1]);
} int main(){
Read();
dfs1(1,0);
dfs2(1,0);
for(int i = 1;i <= m;i++){
trans(i);
ll res = LCAans(rest[i].node1,rest[i].node2);
if(res < INF) cout << res << endl;
else cout << "-1" << endl;
}
return 0;
}

100分

#include<bits/stdc++.h>
#define ll long long
#define maxn 100005
#define INF 1e12 using namespace std; int n,m,val[maxn];
int head[maxn],cnt;
int deep[maxn],father[maxn][25];
char type[100];
ll dpx[2],dpy[2];
ll f[maxn][2],ff[maxn][2];
ll fa[maxn][25][2][2]; struct Edge{
int u,v;
Edge(int a = 0,int b = 0){
u = head[a];
v = b;
}
}e[maxn<<1];
struct Rest{
int node1,node2;
int val1,val2;
}rest[maxn]; void Read(){
int a,b;
cin >> n >> m >> type;
for(int i = 1;i <= n;i++) cin >> val[i];
for(int i = 1;i < n;i++){
cin >> a >> b;
e[++cnt] = Edge(a,b);
head[a] = cnt;
e[++cnt] = Edge(b,a);
head[b] = cnt;
}
for(int i = 1;i <= m;i++){
cin >> rest[i].node1 >> rest[i].val1;
cin >> rest[i].node2 >> rest[i].val2;
}
} void dfs1(int u,int fa){
f[u][0] = 0;
f[u][1] = val[u]; father[u][0] = fa;
deep[u] = deep[fa] + 1; for(int i = head[u];i;i = e[i].u){
int ev = e[i].v;
if(ev == fa) continue;
dfs1(ev,u);
f[u][0] += f[ev][1];
f[u][1] += min(f[ev][1],f[ev][0]);
}
} void dfs2(int u,int fa){
for(int i = head[u];i;i = e[i].u){
int ev = e[i].v;
if(ev == fa) continue; ff[ev][0] = ff[u][1] + f[u][1] - min(f[ev][0],f[ev][1]);
ff[ev][1] = min(ff[u][0] + f[u][0] - f[ev][1],ff[ev][0]);
dfs2(ev,u);
}
} void LCA(){
for(int i = 1;i <= n;i++){
int t = father[i][0];
fa[i][0][0][0] = INF;
fa[i][0][0][1] = f[t][1] - min(f[i][0],f[i][1]);
fa[i][0][1][0] = f[t][0] - f[i][1];
fa[i][0][1][1] = fa[i][0][0][1];
}
for(int t = 1;t <= 20;t++){
for(int i = 1;i <= n;i++){
int X = father[i][t-1];
father[i][t] = father[X][t-1];
fa[i][t][0][0] = min(fa[i][t-1][0][0] + fa[X][t-1][0][0],
fa[i][t-1][0][1] + fa[X][t-1][1][0]);
fa[i][t][0][1] = min(fa[i][t-1][0][0] + fa[X][t-1][0][1],
fa[i][t-1][0][1] + fa[X][t-1][1][1]);
fa[i][t][1][0] = min(fa[i][t-1][1][0] + fa[X][t-1][0][0],
fa[i][t-1][1][1] + fa[X][t-1][1][0]);
fa[i][t][1][1] = min(fa[i][t-1][1][0] + fa[X][t-1][0][1],
fa[i][t-1][1][1] + fa[X][t-1][1][1]);
}
}
} ll LCAans(int x,int y,int val1,int val2){
ll tx[2] = {0,0};
ll ty[2] = {0,0}; dpx[0] = dpx[1] = INF;
dpy[0] = dpy[1] = INF; if(deep[x] < deep[y]){
swap(x,y);
swap(val1,val2);
} dpx[val1] = f[x][val1];
dpy[val2] = f[y][val2]; for(int t = 20;t >= 0;t--){
if((deep[x] - deep[y]) >= (1<<t)){
tx[0] = min(fa[x][t][0][0] + dpx[0],
fa[x][t][1][0] + dpx[1]);
tx[1] = min(fa[x][t][0][1] + dpx[0],
fa[x][t][1][1] + dpx[1]);
dpx[0] = tx[0];
dpx[1] = tx[1];
x = father[x][t];
}
} if(x == y) return dpx[val2] + ff[x][val2]; for(int t = 20;t >= 0;t--){
if(father[x][t] != father[y][t]){
tx[0] = min(fa[x][t][0][0] + dpx[0],
fa[x][t][1][0] + dpx[1]);
tx[1] = min(fa[x][t][0][1] + dpx[0],
fa[x][t][1][1] + dpx[1]);
dpx[0] = tx[0];
dpx[1] = tx[1];
x = father[x][t]; ty[0] = min(fa[y][t][0][0] + dpy[0],
fa[y][t][1][0] + dpy[1]);
ty[1] = min(fa[y][t][0][1] + dpy[0],
fa[y][t][1][1] + dpy[1]);
dpy[0] = ty[0];
dpy[1] = ty[1];
y = father[y][t];
}
} int t = father[x][0];
ll lca_0 = f[t][0] - f[x][1] - f[y][1] + dpx[1] + dpy[1] + ff[t][0];
ll lca_1 = f[t][1] - min(f[x][0],f[x][1]) - min(f[y][0],f[y][1])
+ min(dpx[0],dpx[1]) + min(dpy[0],dpy[1]) + ff[t][1];
return min(lca_0,lca_1);
} int main(){
Read();
dfs1(1,0);
dfs2(1,0);
LCA();
for(int i = 1;i <= m;i++){
ll res = LCAans(rest[i].node1,rest[i].node2,rest[i].val1,rest[i].val2);
if(res < INF) cout << res << endl;
else cout << "-1" <<endl;
}
return 0;
}

[C++]P5024 树形DP 保卫王国的更多相关文章

  1. [倍增][换根DP]luogu P5024 保卫王国

    题面 https://www.luogu.com.cn/problem/P5024 分析 可以对有限制的点对之间的链进行在倍增上的DP数组合并. 需要通过一次正向树形DP和一次换根DP得到g[0][i ...

  2. 『保卫王国 树上倍增dp』

    保卫王国 Description Z 国有n座城市,n - 1条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达. Z 国的国防部长小 Z 要在城市中驻扎军队.驻扎军队需 ...

  3. 【NOIP 2018】保卫王国(动态dp / 倍增)

    题目链接 这个$dark$题,嗯,不想说了. 法一:动态$dp$ 虽然早有听闻动态$dp$,但到最近才学,如果你了解动态$dp$,那就能很轻松做出这道题了.故利用这题在这里科普一下动态$dp$的具体内 ...

  4. 竞赛题解 - NOIP2018 保卫王国

    \(\mathcal{NOIP2018}\) 保卫王国 - 竞赛题解 按某一个炒鸡dalao名曰 taotao 的话说: \(\ \ \ \ \ \ \ \ \ "一道sb倍增题" ...

  5. 「NOIP2018」保卫王国

    「NOIP2018保卫王国」 题目描述 有一棵 \(n\) 个点, 点有点权 \(a_i\),\(m\) 组询问, 每次求钦点两个节点必须选或者必须不选后的树上最小点覆盖. \(1 \leq n, m ...

  6. [Swust OJ 402]--皇宫看守(树形dp)

    题目链接:http://acm.swust.edu.cn/problem/402/ Time limit(ms): 5000 Memory limit(kb): 65535   Description ...

  7. noip2018 d2t3 保卫王国 解题报告

    保卫王国 电脑卡懒得把题面挪过来了. 朴素 \[ dp_{i,0}=\sum dp_{s,1}\\ dp_{i,1}=\sum \min(dp_{s,0},dp_{s,1})+p_i \] 然后直接动 ...

  8. 2018.09.06 警卫安排(树形dp)

    描述 太平王世子事件后,陆小凤成了皇上特聘的御前一品侍卫. 皇宫以午门为起点,直到后宫嫔妃们的寝宫,呈一棵树的形状:有边直接相连的宫殿可以互相望见.大内保卫森严,三步一岗,五步一哨,每个宫殿都要有人全 ...

  9. LG5024 保卫王国

    题意 题目描述 Z 国有\(n\)座城市,\(n - 1\)条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达. Z 国的国防部长小 Z 要在城市中驻扎军队.驻扎军队需要 ...

  10. 【树形DP】MZOJ_1063_士兵守卫

    本题也是这三天来在下写的几篇树形DP之一,但是不知道为什么洛谷上面老是unknown error,...直接去了UVa,说我编译错误...我在想是不是头文件的原因,于是被逼无奈,交了一道c89的代码. ...

随机推荐

  1. docker 公有仓库与私有仓库常见操作

    本文为博主原创,转载请注明出处: 自建一个Docker仓库,可以使用Docker官方提供的开源项目Docker Registry.以下是一些基本步骤: 安装Docker Registry: 在服务器上 ...

  2. JVM虚拟机栈

    JVM虚拟机栈 1.概述 1.1背景 由于跨平台性的设计,Java的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 优点是跨平台,指令集小,编译器容易实现,缺点是性能下降 ...

  3. Openjob 1.0.5 发布,新增 Agent

    什么是 Openjob? Openjob 基于Akka架构的新一代分布式任务调度框架.支持多种定时任务.延时任务.工作流设计,采用无中心化架构,底层使用一致性分片算法,支持无限水平扩容. 完善的任务日 ...

  4. 2023-07-25:你驾驶出租车行驶在一条有 n 个地点的路上 这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n 通过接乘客订单盈利。你只能沿着编号递增的方向前进,不能改变方向 乘

    2023-07-25:你驾驶出租车行驶在一条有 n 个地点的路上 这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n 通过接乘客订单盈利.你只能沿着编号递增的方向前进,不能改变方向 乘 ...

  5. 2021-11-18 wpf模板

    自定义模板 <ControlTemplate x:Key="ButtonStyle1" TargetType="Button"> <Borde ...

  6. Linux 下的 OpenGL 之路(九):天空盒、反射和折射

    前言 搞定了天空盒,才算是真正完成了场景的搭建,以后再要进行什么样的图形学测试,都可以在这个场景下进行.比如后面的反射.折射就是这样的例子. 写完这篇,我决定暂时结束这个系列.主要是因为我太懒了,居然 ...

  7. webpack配置文件的分离

    配置文件的分离 目的就是让开发环境, 生产环境,测试环境的配置分隔开 步骤一: 在项目根目录下创建一个 build 文件夹专门用来存放配置文件,再创建三个js文件, base.config.js 文件 ...

  8. pandas读取mysql并导出为excel

    前言 业务需要从数据库导出数据为excel,并设置成自动化.这里用pandas写的数据导入导出,还算方便.配合crontab + shell脚本使用,每天晚上自动生成excel,然后cp到指定目录.s ...

  9. Python实现输入三个整数x,y,z,请把这三个数由小到大输出;

    num1=input('请输入第一个数,x:') num2=input('请输入第二个数,y:') num3=input('请输入第三个数,z:') if num1>num2: # if 语句判 ...

  10. mall :sa-token项目源码解析

    目录 一.mall开源项目 1.1 来源 1.2 项目转移 1.3 项目克隆 二.Sa-Toekn框架 2.1 Sa-Token 简介 2.2 分布式后端项目的使用流程 2.3 分布式后端项目的使用场 ...