題意
在一棵形如樹的圖上有一些炸藥點,在樹上任意選擇一些點爲點火點,使得最晚爆炸的炸藥最早(即炸藥點到點火點的最大距離最小)。
特性
對於一個時間,顯然剛好在這個時間到頭時爆炸是最優的。
題解
二分+貪心+樹形DP
題意轉化一下,用一些點,擴張最小的長度覆蓋最小的關鍵點。
看到最大值最小首先想到二分(其原因我是今天才明白,最大值可以無限變大,一定有一個最小點,小於這個點的解不合法,大於這個點的解不優秀,這個就是二分性在一個題目下的變形)。
check的時候用樹形DP,設f1[x]爲x子樹內未覆蓋點距離,f2[x]爲x子樹剩餘可以覆蓋長度。
再根據貪心,能不設儘量不設。
如果不設,那麼要向f節點反饋一下最遠的炸藥點的距離。
設的話可以覆蓋x以上一段距離以內的點。
如果最終所需數小於允許設的點火點數,return true。
代碼
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=(1<<30)-1;
const int maxn=300010;
int n,m,mid;
int dyn[maxn];
struct E{int y,next;}e[maxn*2];int len=1,last[maxn];
void ins(int x,int y)
{
e[++len]=(E){y,last[x]};last[x]=len;
}
int tot;
int f1[maxn],f2[maxn];//f1未覆蓋點距離,f2剩餘可以覆蓋長度
void dfs(int x,int fa)
{
f1[x]=dyn[x]?0:-inf;f2[x]=-inf;
for(int k=last[x];k;k=e[k].next)
{
int y=e[k].y;
if(y==fa) continue;
dfs(y,x);
f1[x]=max(f1[x],f1[y]+1);
f2[x]=max(f2[x],f2[y]-1);
}
if(f1[x]>f2[x])//子樹內不能自己解決
{
if(f1[x]==mid)//貪心,必須放
{
tot++;
f1[x]=-inf;
f2[x]=mid;
}
}
else f1[x]=-inf;//自己解決掉
}
bool check()
{
tot=0;
dfs(1,0);
if(f1[1]>=0) tot++;
return tot<=m;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&dyn[i]);
for(int i=1;i<n;i++)
{
int x,y;scanf("%d%d",&x,&y);
ins(x,y);ins(y,x);
}
int l=0,r=inf,ans=-1;
while(l<=r)
{
mid=l+r>>1;
if(check()) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
return 0;
}