CF868E Policeman and a Tree
CLVI.CF868E Policeman and a Tree
DP是很容易想的。但是如何设计状态呢?
一开始我自己假设了一个结论:在警察出发前,所有罪犯会排成此时的最优方案,然后不动;然后在警察抓到一个罪犯后,所有罪犯会再度排成最优方案,之后就一直不动了。但是如果这样做的话 的数据范围就像在开玩笑一样,因此我不确定这个结论是否正确。
但是这个 的数据范围就意味着我们不需要顾忌那么多,可以放心在状态里设一大坨东西。于是我们设 表示当前警察在边 (看作有向边)半道上,剩 个罪犯, 个在该边的终点侧。转移有两类:一种是该边的终点(设为 )是叶子节点,警察抓住了叶子上所有小偷,所以 ,其中 是 的反边,而 是边权。另一种是 并非叶子节点,此时罪犯可以先分布成最优状态,等警察走到其中某个叶子上。警察想要最优,于是罪犯就必须让警察的最优选择是所有情形中最劣的。于是就有 。
暴力转移是 的,因为要拼接两半的背包,但已经足以通过。但是,通过观察性质,可以进一步优化。该性质是,随着状态由 变为 ,最优的 的分配只会是在上一个 的分配的基础上使某个位置加一得到。更具体地,该加一的位置一定是所有位置中加一后结果最大的那个。证明如下:
因为是最小值最大的形式,就考虑二分。二分后,我们就只能保留所有 某一值的DP状态。而又显然 随着 的递增是不增的(追的人越多,结束得越早),故保留的只能是每个 的前缀。每次取最大的转移是最有可能拉高DP水平的。
于是直接用大根堆维护即可。时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,S,f[120][60][60],head[60],deg[60],cnt,sz[60],res=0x3f3f3f3f;//policeman on edge i;j terrorists are remaining;k terrorists on his direction
struct node{int to,next,val;}edge[120];
void ae(int u,int v,int w){
edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
}
void DP(int id,int u);
int F(int x,int y,int z){if(!y)return 0;if(f[x][y][z]==-1)DP(x,y);return f[x][y][z];}
struct dat{
int x,y,z;
dat(int X,int Y,int Z){x=X,y=Y,z=Z;}
int val()const{return z<y?F(x,y,z+1):0;}
friend bool operator<(const dat&u,const dat&v){return u.val()<v.val();}
};
void DP(int id,int u){
f[id][u][0]=0x3f3f3f3f;
if(deg[edge[id].to]==1){for(int i=1;i<=u;i++)f[id][u][i]=F(id^1,u-i,u-i)+edge[id].val;return;}
priority_queue<dat>q;
for(int i=head[edge[id].to];i!=-1;i=edge[i].next)if((i^id)!=1)q.emplace(i,u,0);
for(int i=1;i<=u;i++){
dat x=q.top();q.pop();
f[id][u][i]=min(f[id][u][i-1],x.val()+edge[id].val);
x.z++,q.push(x);
}
}
void dfs(int x,int fa){for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].to!=fa)dfs(edge[i].to,x),sz[x]+=sz[edge[i].to];}
int main(){
scanf("%d",&n),memset(head,-1,sizeof(head)),memset(f,-1,sizeof(f));
for(int i=1,x,y,z;i<n;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z),deg[x]++,deg[y]++;
scanf("%d%d",&S,&m);
for(int i=1,x;i<=m;i++)scanf("%d",&x),sz[x]++;
dfs(S,0);
for(int i=head[S];i!=-1;i=edge[i].next)res=min(res,F(i,m,sz[edge[i].to]));
printf("%d\n",res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?