CF868E Policeman and a Tree

CLVI.CF868E Policeman and a Tree

DP是很容易想的。但是如何设计状态呢?

一开始我自己假设了一个结论:在警察出发前,所有罪犯会排成此时的最优方案,然后不动;然后在警察抓到一个罪犯后,所有罪犯会再度排成最优方案,之后就一直不动了。但是如果这样做的话 \(50\) 的数据范围就像在开玩笑一样,因此我不确定这个结论是否正确。

但是这个 \(50\) 的数据范围就意味着我们不需要顾忌那么多,可以放心在状态里设一大坨东西。于是我们设 \(f_{i,j,k}\) 表示当前警察在边 \(i\)(看作有向边)半道上,剩 \(j\) 个罪犯,\(k\) 个在该边的终点侧。转移有两类:一种是该边的终点(设为 \(x\))是叶子节点,警察抓住了叶子上所有小偷,所以 \(f_{i,j,k}\leftarrow f_{i',j-k,j-k}+w\),其中 \(i'\)\(i\) 的反边,而 \(w\) 是边权。另一种是 \(x\) 并非叶子节点,此时罪犯可以先分布成最优状态,等警察走到其中某个叶子上。警察想要最优,于是罪犯就必须让警察的最优选择是所有情形中最劣的。于是就有 \(f_{i,j,k}\leftarrow\max\limits_{c\text{ is an assignment of }k\text{ into }x\text{'s sons}}\{\min\limits_{y\in\text{son}_x}f_{x\rightarrow y,j,c_y}+w\}\)

暴力转移是 \(O(n^5)\) 的,因为要拼接两半的背包,但已经足以通过。但是,通过观察性质,可以进一步优化。该性质是,随着状态由 \(f_{i,j,k}\) 变为 \(f_{i,j,k+1}\),最优的 \(c\) 的分配只会是在上一个 \(c\) 的分配的基础上使某个位置加一得到。更具体地,该加一的位置一定是所有位置中加一后结果最大的那个。证明如下:

因为是最小值最大的形式,就考虑二分。二分后,我们就只能保留所有 \(\geq\) 某一值的DP状态。而又显然 \(f_{i,j,k}\) 随着 \(k\) 的递增是不增的(追的人越多,结束得越早),故保留的只能是每个 \(i,j\) 的前缀。每次取最大的转移是最有可能拉高DP水平的。

于是直接用大根堆维护即可。时间复杂度 \(O(n^3\log n)\)

代码:

#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;
}

posted @ 2021-03-31 15:42  Troverld  阅读(46)  评论(0编辑  收藏  举报