LgP9983 [USACO23DEC] Cowntact Tracing P

神。可惜自己实力不行。/ll

题意不说了。难点其实是想到一个合理的贪心方法。才能进一步优化。

考虑全部为 \(1\) 的,容易想到的贪心策略是找到一个深度最大的且未被覆盖的点,找到这个点的 \(k\) 级祖先 \(x\),将 \(x\) 设为初始点,知道所有点都被覆盖。

考虑有 \(0\) 的情况。记录 \(td_u\) 表示每个点到未感染点的最小距离。显然只有 \(td_u>k\) 的点 \(u\) 才有可能作为初始点。考虑类似上面的贪心过程。每次找到一个深度最大的且未被覆盖的感染点。找到一个能作为初始点的深度最小的点 \(x\),使得 \(x\) 能覆盖 \(u\),令 \(x\) 为初始点。如此,知道所有感染点都被覆盖。直接暴力做可以做到 \(O(n^2Q)\)。这其实是很自然的思考过程。证明显然。

考虑优化这个贪心即可。假如我们每次能找到 \(x\) 的话,只需要按深度从大到小枚举 \(u\),判断 \(u\) 是否被覆盖即可。考虑点分树,记 \(lw_u\) 表示u的点分树种到点 \(u\) 的最短距离,查询和修改每次枚举\(u\) 在点分树上祖先即可。复杂度 \(O(\log n)-O(\log n)\)。

问题在于已知 \(u\),如何找到 \(x\)。考虑 \(p=lca(u,x)\),因为 \(u\) 是深度最大的点,显然在这之后 \(p\) 的子树都被覆盖,那么贪心的,我们希望 \(p\) 深度越小越好。那么我们记录 \(pv_u\) 表示里 \(u\) 最近的可初始点。相当于 \(dep_u-dep_p + dis(p,pv_p)\le k\)。记录 \(b_x=dep_x+k-dis(x,pv_x)\)。相当于 \(b_x\le dep_u\),显然 \(b_{fa_x}\le b_x\),因为可以让 \(fa_x\)选的点和 \(x\) 选的点相同。倍增找到深度最小的 \(p\) 即可。

点击查看代码
#include<bits/stdc++.h>
#define Pr pair<int,int>
#define fi first
#define nd second
using namespace std;
typedef long long LL;
inline int read(){
	char ch=getchar();
	while(!isdigit(ch) && ch!='-') ch=getchar();
	int x=0,ff=1; if(ch=='-') ff=-1,ch=getchar();
	while(isdigit(ch)) x=(x<<3) + (x<<1) + (ch^48),ch=getchar();
	return x*ff;
}
const int N=1e5+5,inf=1e9;
int n,Q,D,va[N],td[N],rT,smn,siz[N],p1[N],dep[N],fa[N][20],ld[N],pv[N],lmd[N]; bool vis[N]; char ch2[N]; vector<int> e[N]; vector<Pr> vf[N];
void findrt(int u,int Fa,int S){ if(!Fa) rT=0,smn=inf;
	siz[u]=1; int mx=0;
	for(int v:e[u]) if(v!=Fa && !vis[v]) findrt(v,u,S), siz[u]+=siz[v],mx=max(mx,siz[v]);
	mx=max(mx,S-siz[u]); if(mx<smn) smn=mx,rT=u;
}
void dfsd(int u,int Fa,int d){
	vf[u].push_back({rT,d});
	for(int v:e[u]) if(v!=Fa && !vis[v]) dfsd(v,u,d+1);
}
void dfz(int u){
	dfsd(u,0,0); vis[u]=1; findrt(u,0,0); 
	for(int v:e[u]) if(!vis[v]) findrt(v,0,siz[v]),dfz(rT);
}
void dfs1(int u,int Fa){
	dep[u]=dep[Fa]+1; fa[u][0]=Fa;
	for(int i=1;i<=18;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v:e[u]) if(v!=Fa) dfs1(v,u);
}
bool chk(int u){ if(!va[u]) return 1;
	for(auto [x,d]:vf[u]) if(lmd[x]+d<=D) return 1;
	return 0;
}
void addw(int u){
	for(auto [x,d]:vf[u]) lmd[x]=min(lmd[x],d);
}
void vmain(){
	D=read(); for(int i=1;i<=n;i++) ld[i]=lmd[i]=inf,pv[i]=0;
	queue<int> q; for(int i=1;i<=n;i++) if(td[i]>D) pv[i]=i,ld[i]=0,q.push(i);
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int v:e[u]) if(!pv[v]) pv[v]=pv[u],ld[v]=ld[u]+1,q.push(v);
	}
	for(int i=1;i<=n;i++) ld[i]=dep[i]+D-ld[i];
	int cnt=0;
	for(int ii=1;ii<=n;ii++) { int u=p1[ii]; if(chk(u)) continue;
		if(ld[u]<dep[u]) {puts("-1"); return ;}
		int x=u; for(int i=18;i>=0;i--) if(fa[x][i] && ld[fa[x][i]]>=dep[u]) x=fa[x][i];
		x=pv[x]; addw(x); cnt++;
	} printf("%d\n",cnt);
}
int main(){
	n=read(); scanf("%s",ch2+1); for(int i=1;i<=n;i++) va[i]=ch2[i]-'0';
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		e[u].push_back(v); e[v].push_back(u);
	} dfs1(1,0);
	for(int i=1;i<=n;i++) td[i]=inf;
	queue<int> q; for(int i=1;i<=n;i++) if(!va[i]) td[i]=0,q.push(i);
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int v:e[u]) if(td[v]==inf) td[v]=td[u]+1,q.push(v);
	} findrt(1,0,n); dfz(rT);
	for(int i=1;i<=n;i++) p1[i]=i;
	sort(p1+1,p1+n+1,[&](int x,int y){return dep[x]>dep[y];});
	Q=read(); while(Q--) vmain();
	return 0;
}
posted @ 2024-02-13 15:30  Shui_dream  阅读(96)  评论(0)    收藏  举报