UOJ Logo kczno1的博客

博客

[Wc2008]游览计划 dp

2017-04-13 16:58:24 By kczno1

总算理解了这个dp。

由于最后的结果一定成一个树形,
我们用dp[s][i]表示包括的关键点集合为s,当前树的根为i的最少花费。
转移有两种,一种是给树扩展一个节点,一种是把两棵树并在一起。
也就是:
1 dp[s][i]=dp[s][j]+a[i] j与i相邻
2 dp[s][i]=dp[x][i]+dp[s-x][i]-a[i] x为s的子集
从小到大枚举s
先做第一个转移 再用当前的s做第二个转移更新更大的s
由于第一个转移是有环的 我们要用spfa来实现

一ac就拿了bzoj rank1。。其实应该还有地方没有卡常。

#include<bits/stdc++.h>

const int S=1<<10,N=101,inf=1<<29;
int n,m,p,k,full;
int a[N],num[N],i,j,dy[12][12];

int fr[S][N],f[S][N];
int t[N];
struct edge
{
    int to,next;
}l[N<<2];int e;
void add_e(int x,int y)
{
    l[++e]=(edge){y,t[x]};t[x]=e;
    l[++e]=(edge){x,t[y]};t[y]=e;
}

int base;
int *g,*from;bool in[N];
int q[100000],head,tail,x,y;
void spfa()
{
    for(i=1;i<=p;++i) q[i]=i;
    tail=p;
    for(head=1;head<=tail;++head)
    {
        x=q[head];in[x]=0;
        for(i=t[x];y=l[i].to;i=l[i].next)
        if(g[y]>g[x]+a[y])
        {
            g[y]=g[x]+a[y];from[y]=base+x;
            if(!in[y]) {in[y]=1;q[++tail]=y;}
        }
    }
}

bool b[N];
void dfs(int s,int x)
{
    if(!s) return ;
    b[x]=1;
    int ns=fr[s][x]/N,y=fr[s][x]%N;
    if(y==x) {dfs(ns,x);dfs(s-ns,x);}
    else dfs(ns,y);
}

int main()
{
    freopen("1.in","r",stdin);freopen("1.out","w",stdout);
    scanf("%d%d",&n,&m);
    int i,j;
    for(i=1;i<=n;++i) 
    for(j=1;j<=m;++j) 
    {
     scanf("%d",a+(++p));
     dy[i][j]=p;
     if(!a[p])num[p]=++k;
    }

    full=(1<<k)-1;
    for(i=1;i<=full;++i)
    for(j=1;j<=p;++j)
     f[i][j]=inf;
    for(k=0,i=1;i<=p;++i)
    if(a[i]) f[0][i]=a[i];
    else f[1<<num[i]-1][i]=0;

    for(i=2;i<=n;++i) 
    for(j=1;j<=m;++j) 
     add_e(dy[i][j],dy[i-1][j]);
    for(j=2;j<=m;++j)
    for(i=1;i<=n;++i)
     add_e(dy[i][j-1],dy[i][j]);

    int s,ns;
    for(s=1;s<=full;++s)
    {
        g=f[s];from=fr[s];
        base=s*N;
        spfa();
        ns=full-s; 
        for(j=ns;j;j=(j-1)&ns)
        if(j<s)
        for(i=1;i<=p;++i)
         if(f[s+j][i]>g[i]+f[j][i]-a[i])
         {
             f[s+j][i]=g[i]+f[j][i]-a[i];
             fr[s+j][i]=base+i;
         }
    }

    int mn=1;
    for(i=2;i<=p;++i)
    if(g[i]<g[mn]) mn=i;
    printf("%d\n",g[mn]);
    dfs(full,mn);

    for(i=1;i<=n;puts(""),++i)
    for(j=1;j<=m;++j) 
    if(num[dy[i][j]]) printf("x"); else
    if(b[dy[i][j]]) printf("o"); else
    printf("_"); 
}

评论

liuyiding
这个dp好神的QAQ 其实dp[s][i]中的s可能包括其他关键点却没有算进去,但是因为这样不可能是最优解所以对答案不会有影响 或者理解为至少连通的集合为s?
kczno1
@liuyiding 我是觉得最终的答案的那棵树一定会被我从每个叶子开始一直合并上去,所以应该是能得到答案的。 虽然这些状态确实会有如你所说的少算关键点的情况发生。

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。