bzoj2525 1426

题面二分最短时间,求出最小的需要引爆数
至于关键点的引爆状态有关
f[i] 以i为根的子树中已经引爆的点离i最近的距离
g[i] 以i为根的子树中未引爆的点离i最远的距离
回溯到每个节点时,优先考虑用另一个儿子中的点覆盖其他儿子
if(f[i]+g[i]<=mid) g[i]=INF
if(g[i]==mid) 必须引爆x

#include 
#include 
#include 
#define  INF 1000000000
#define maxn 300005
using namespace std;
int n,m;
int a[maxn];
int ans;
struct edge{
    int to,ne;
}b[maxn*2];
int k=0,head[maxn];
int f[maxn],g[maxn];
int limit;
int num=0,op1=0;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
    return x*f;
}

void add(int u,int v)
{
    k++;
    b[k].to=v; b[k].ne=head[u]; head[u]=k;
}
void dfs(int x,int pre)
{
    f[x]=INF;
    if(a[x]) g[x]=0;
    else g[x]=-INF;
    for(int i=head[x];i!=-1;i=b[i].ne)
    if(b[i].to!=pre){
        dfs(b[i].to,x);
        f[x]=min(f[x],f[b[i].to]+1);
        g[x]=max(g[x],g[b[i].to]+1);
    }
    if(g[x]+f[x]<=limit)  g[x]=-INF;
    if(g[x]==limit){
        f[x]=0,g[x]=-INF,num++;
    }
}

bool check(int x)
{
    if(!x) return op1<=m;
    limit=x; num=0;
    dfs(1,0);
    //printf("%d %d %d %d\n",x,f[1],g[1],num);
    if(g[1]+f[1]>limit) num++;
    return num<=m;
}

void getans()
{
    int l=0,r=n,mid;
    ans=n;
    while(l<=r){
        mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    memset(head,-1,sizeof(head));
    n=read(); m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        if(a[i]) op1++;
    }
    int x,y;
    for(int i=1;ix=read(); y=read();
        add(x,y); add(y,x);
    }
    getans();
    printf("%d\n",ans);
    //while(1);
    return 0;
}

题面
考虑逆推
f[i] 已经买了i张邮票,距离n张邮票还需要的购买次数

f[i]=inf[i]+ninf[i+1]+1

f[i]=f[i+1]+nni

g[i]表示所需钱数
假设每张邮票需要一元,后面购买的邮票都会贵1元
g[i]=in(g[i]+f[i])+nin(g[i+1]+f[i+1])+1

你可能感兴趣的:(bzoj)