BZOJ 4006 [JLOI2015]管道连接

斯坦纳树DP。

首先记 f[i][sta] 表示经过节点 i 节点状态sta的最小花费,进行斯坦纳树DP

再记 g[s] 表示颜色状态 s 的最小花费,注意是颜色状态,每一位表示一个颜色集合,那么g[s]可以从 f[i][sta] O(p2p) 的时间转移过来(为什么我的复杂度是这个可以研究一下代码- -)

显然直接用 g[2p1] 数组作为答案是不对的,因为不同频道可以不连通。于是再DP一次,就能成功地把不连通的情况考虑进去啦,方程:

g[sta]=min(g[sta],g[s]+g[stas]),(ssta)

为什么这个方程是正确的?因为他能够直接相加而不考虑交点呀。。。

代码又丑又慢,改了半天,跑了十多秒QAQ

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 20000005
#define N 1005
#define M 3005
#define P 11
using namespace std;
struct edge{int next,to,v;}e[M<<1];
struct color{int col,id;}c[P];
queue<int> q;
int cnt=1, n, m, p, last[N], col_sta[P], f[N][1<<P], g[1<<P], key_cnt=0, state=0;
bool inq[N];
void add(int a, int b, int c)
{
    e[++cnt]=(edge){last[a],b,c};
    last[a]=cnt;
}
void SPFA(int k)
{
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        inq[x]=0;
        for(int i = last[x]; i; i=e[i].next)
        {
            int y=e[i].to;
            if(f[x][k]+e[i].v<f[y][k])
            {
                f[y][k]=f[x][k]+e[i].v;
                if(!inq[y])
                {
                    inq[y]=1;
                    q.push(y);
                }
            }
        }
    }
}
bool cmp(color a, color b){return a.col<b.col;}
int main()
{
    memset(f,63,sizeof(f));
    scanf("%d%d%d",&n,&m,&p);
    for(int a, b, v, i = 0; i < m; i++)
    {
        scanf("%d%d%d",&a,&b,&v);
        add(a,b,v);
        add(b,a,v);
    }
    for(; key_cnt < p; key_cnt++)
        scanf("%d%d",&c[key_cnt+1].col,&c[key_cnt+1].id);
    sort(c+1,c+1+key_cnt,cmp);
    int col_cnt=0;
    for(int i = 1; i <= p; i++)
        if(c[i].col!=c[i-1].col) col_cnt++;
    c[0].col=c[1].col;
    for(int i = 1, pos=1; i <= col_cnt; i++)
    {
        while(c[pos].col==c[pos-1].col)
            c[pos-1].col=i,pos++;
        c[pos-1].col=i;
        pos++;
    }
    for(int i = 1; i <= p; i++)
    {
        col_sta[c[i].col]|=1<<(i-1);
        f[c[i].id][1<<(i-1)]=0;
    }
    for(int sta=1; sta<(1<<key_cnt); sta++)
    {
        for(int j = 1; j <= n; j++)
        {
            for(int s=sta&(sta-1); s; s=(s-1)&sta)
                f[j][sta]=min(f[j][sta],f[j][s]+f[j][sta-s]);
            if(f[j][sta]<INF)
            {
                q.push(j);
                inq[j]=1;
            }
        }
        SPFA(sta);
        for(int i = 1; i <= n; i++)
            f[0][sta]=min(f[0][sta],f[i][sta]);
    }
    for(int i = 1; i < (1<<col_cnt); i++)
    {
        int j = i, t = 0;
        for(int k = 1; j; j>>=1, k++)
        {
            if(j&1)
                t|=col_sta[k];
        }
        g[i]=f[0][t];
    }
    for(int sta=1; sta<(1<<col_cnt); sta++)
        for(int s=sta&(sta-1); s; s=(s-1)&sta)
            g[sta]=min(g[sta],g[s]+g[sta-s]);
    printf("%d\n",g[(1<<col_cnt)-1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值