[CF868E]Policeman and a Tree

5 篇文章 0 订阅

题目大意

给你一颗有n个点的树,每条边有边权,有一个警察一开始在点S,他的速度是1,即通过一条长度为x的边要花x单位时间。
有m个罪犯,一开始第i个在点x[i],他们的速度无限快。
如果罪犯和警察到达同一个点,那么罪犯会被抓住。
现在罪犯们想最大化最后一个被抓的时间,警察想最小化抓的时间。
n<=50

解题思路

不看wxh的博客都不会qwq
我们从一开始的局面考虑。
警察在点S,现在罪犯分布在各个子树里,假设子树x原本有siz[x]个罪犯,警察会选一个子树走进去,使得时间最小,而每颗子树的罪犯则会走动,使得警察难以抓住。假如他选子树x走进去,那么就变成另外一个问题了,警察在点x,从S走来,x子树里有siz[x]个罪犯,算上这些还有m个罪犯没有被抓。
现在,x子树内的罪犯又会重新分配位置,然后继续决策。直到警察走到一个叶子结点,抓了一些罪犯,那么总罪犯减少,又变成子问题了。
所以设f(x,y,cnt,tot)。四元组状态表示警察在x点,他从y走来(最优决策的话,走到叶子前不可能折返),当前x子树有cnt个罪犯(分布还未确定),总共还有tot个罪犯没被抓。f()表示在此状态下警察抓完所有人的最短时间。
考虑转移。
边界:如果到叶子结点,那么抓到cnt个人,tot-=cnt,返回去继续抓。抓完了返回0。如果cnt=0,走进这棵子树没有意义,返回inf。
现在假设x点有k个儿子,设在第i个儿子分布了a[i]个罪犯,其中 i=1..ka[i]=cnt ∑ i = 1.. k a [ i ] = c n t ;假设a[]已经确定,警察为了时间尽量短,选择f(son[i],x,a[i],tot)最小的那个i;反过来,罪犯为了使得抓捕时间长,一定会使得a[]里面f值最小的儿子尽量大。这个用dp实现就可以求出最终的抓捕时间,就是把cnt个元素分k份的过程。
时间复杂度 O(n5) O ( n 5 )

代码

#include<cstdio> 
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
//开 O2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
typedef long long ll;
typedef long long LL;
typedef double db;
const int N=50+5,mo=1e9+7;
int f[N][N][N][N],siz[N],ans,n,i,j,k,l,x,y,z,S,m;
int tt,b[N*2],c[N*2],nxt[N*2],fst[N];
void cr(int x,int y,int z)
{
    tt++;
    b[tt]=y;
    c[tt]=z;
    nxt[tt]=fst[x];
    fst[x]=tt;
}
int dfs(int x,int y,int cnt,int tot,int z)
{
    if (!tot) return 0;
    if (!cnt) return 1e9;
    int &F=f[x][y][cnt][tot];
    int i,j,k,ts=0;
    if (F!=1e9) return F;
    if (b[fst[x]]==y&&!nxt[fst[x]]) return F=z+dfs(y,x,tot-cnt,tot-cnt,z);
    for(int p=fst[x];p;p=nxt[p])
        if (b[p]!=y)
        {
            fo(i,0,cnt)
                dfs(b[p],x,i,tot,c[p]);
            ts++;
        }
    int g[N][N];
    fo(i,0,ts) fo(j,0,tot) g[i][j]=0;
    ts=0;
    g[0][0]=1e9;
    for(int p=fst[x];p;p=nxt[p])
        if (b[p]!=y)
        {
            ++ts;
            fo(j,0,tot)
                fo(k,0,tot-j)
                    g[ts][j+k]=max(g[ts][j+k],min(g[ts-1][j],dfs(b[p],x,k,tot,c[p])));
        }
    return F=g[ts][cnt]+z;
}
void thr(int x,int y)
{
    for (int p=fst[x];p;p=nxt[p])
        if (b[p]!=y)
        {
            thr(b[p],x);
            siz[x]+=siz[b[p]];
        }
}
int main()
{
    freopen("e.in","r",stdin);
    //freopen("e.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n-1)
    {
        scanf("%d %d %d",&x,&y,&z);
        cr(x,y,z);
        cr(y,x,z);
    }
    scanf("%d %d",&S,&m);
    fo(i,1,m)
    {
        scanf("%d",&x);
        siz[x]++;
    }
    thr(S,0);
    fo(i,1,n) fo(j,1,n) fo(k,0,m) fo(l,0,m) f[i][j][k][l]=1e9;
    ans=1e9;
    for(int p=fst[S];p;p=nxt[p])
        ans=min(ans,dfs(b[p],S,siz[b[p]],m,c[p]));
    printf("%d\n",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值