BZOJ2681 : 玩遊戲2

Claris發表於2018-08-22

首先若存在多個連通塊,那麼答案顯然是$+\infty$。

否則以$m$為根,每棵子樹的根節點都最多隻能放一個金幣,且這些子樹之間互不干擾。

對於一棵父親為$m$的子樹,最優方案下一定可以將子樹剖分成若干條祖先到孫子的鏈,每條鏈中每個點$x$往上貢獻$\lfloor\frac{v[x]}{2}\rfloor$個金幣,且不能貢獻到其它鏈上去,因此一條有$k$個點的鏈最多可以放$2^k-1$個金幣。

設$f[i][j]$表示考慮$i$的子樹,$i$所在鏈裡有$j$個點時最多能放的金幣數,列舉鏈的接法轉移即可。

時間複雜度$O(n^2)$。

 

#include<cstdio>
typedef long long ll;
const int N=70;
const ll inf=1LL<<50;
int n,m,i,j,ed,g[N],v[N<<1],nxt[N<<1];char s[N];ll p[N],f[N][N],h[N],ans;
inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
inline void up(ll&a,ll b){
  if(b>inf)b=inf;
  if(a<b)a=b;
}
void dfs(int x,int y){
  int i,j,k,u;
  for(i=0;i<=n;i++)f[x][i]=-1;
  f[x][1]=0;
  for(i=g[x];i;i=nxt[i]){
    u=v[i];
    if(u==y)continue;
    dfs(u,x);
    for(j=0;j<=n;j++)h[j]=-1;
    ll tmp=0;
    for(j=1;j<=n;j++)if(~f[x][j]){
      up(h[j],f[x][j]+f[u][0]);
      up(tmp,f[x][j]+p[j-1]);
    }
    for(j=1;j<n;j++)if(~f[u][j])up(h[j+1],f[u][j]+tmp);
    for(j=0;j<=n;j++)f[x][j]=h[j];
  }
  for(i=1;i<=n;i++)if(~f[x][i])up(f[x][0],f[x][i]+p[i]);
}
int main(){
  for(p[0]=i=1;i<N;i++)up(p[i],p[i-1]*2);
  for(i=0;i<N;i++)p[i]--;
  while(~scanf("%d%d",&n,&m)){
    for(i=1;i<=n;i++)g[i]=0;
    for(ed=0,i=1;i<=n;i++){
      scanf("%s",s+1);
      for(j=1;j<=n;j++)if(s[j]=='Y')add(i,j);
    }
    if(ed/2!=n-1){
      puts("-1");
      continue;
    }
    ans=0;
    for(i=g[m];i;i=nxt[i]){
      dfs(v[i],m);
      up(ans,ans+f[v[i]][0]);
    }
    if(ans>2000000000)ans=-1;
    printf("%lld\n",ans);
  }
  return 0;
}

  

相關文章