斯坦纳树

斯坦纳树

就是一个很暴力的东西。考虑要做最小生成树,其中一些点必须选,一些点可选可不选。必选点比较少,可以用状压维护。

按照状压状态从小到大更新,每次先枚举子集更新自己,再跑最短路更新全局。

复杂度\(O(n 3^n)\),感觉特弱智。

WC2008 游览计划

img

对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,216]的范围内

题解

此题就是斯坦纳树板子题,没什么好说的。重点在于理解更新顺序。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=11,S=1025,INF=0x3f3f3f3f;
int n,m,tot;
int a[N][N],f[N][N][S];
co int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int vis[N][N];
struct node{int x,y,s;}pre[N][N][S];

queue<pair<int,int> > q;
void spfa(int cur){
    while(q.size()){
        int x=q.front().first,y=q.front().second;
        q.pop(),vis[x][y]=0;
        for(int i=0;i<4;++i){
            int nx=x+dx[i],ny=y+dy[i]; // edit 1:dy
            if(nx<1||nx>n||ny<1||ny>m) continue;
            if(f[nx][ny][cur]>f[x][y][cur]+a[nx][ny]){
                f[nx][ny][cur]=f[x][y][cur]+a[nx][ny];
                pre[nx][ny][cur]=(node){x,y,cur};
                if(!vis[nx][ny]) q.push(make_pair(nx,ny)),vis[nx][ny]=1;
            }
        }
    }
}
void dfs(int x,int y,int now){
    vis[x][y]=1;
    node t=pre[x][y][now];
    if(!t.x&&!t.y) return;
    dfs(t.x,t.y,t.s);
    if(t.x==x&&t.y==y) dfs(t.x,t.y,now-t.s);
}
int main(){
    read(n),read(m);
    memset(f,0x3f,sizeof f);
    for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
        if(!read(a[i][j])) f[i][j][1<<tot]=0,++tot;
    int lim=(1<<tot)-1;
    for(int sta=0;sta<=lim;++sta){
        for(int i=1;i<=n;++i)for(int j=1;j<=m;++j){
            for(int s=sta;s;s=(s-1)&sta)
                if(f[i][j][s]+f[i][j][sta-s]-a[i][j]<f[i][j][sta]){
                    f[i][j][sta]=f[i][j][s]+f[i][j][sta-s]-a[i][j];
                    pre[i][j][sta]=(node){i,j,s};
                }
            if(f[i][j][sta]<INF) q.push(make_pair(i,j)),vis[i][j]=1;
        }
        spfa(sta);
    }
    int ansx,ansy;
    for(int i=1,flag=0;i<=n&&!flag;++i)
        for(int j=1;j<=m;++j)if(!a[i][j]){
            ansx=i,ansy=j,flag=1;break;
        }
    printf("%d\n",f[ansx][ansy][lim]);
    memset(vis,0,sizeof vis);
    dfs(ansx,ansy,lim);
    for(int i=1;i<=n;++i,puts(""))
        for(int j=1;j<=m;++j){
            if(!a[i][j]) putchar('x');
            else vis[i][j]?putchar('o'):putchar('_');;
        }
    return 0;
}

BZOJ4774 修路

村子间的小路年久失修,为了保障村子之间的往来,法珞决定带领大家修路。

对于边带权的无向图 G = (V, E),请选择一些边,使得1 <= i <= d, i号节点和 n - i + 1 号节点可以通过选中的边连通,最小化选中的所有边的权值和。

1 <= d <= 4,2d <= n <= 104,0 <= m <= 104

题解

这题的特殊点不需要两两连通,所以求的是最小森林。

还是考虑状压,\(f(S,i)\)表示状态为\(S\)的点集连成的生成树的根为\(i\)的最小代价,用斯坦纳树做法转移即可。

每次用\(\min_i f(S,i)\)的值更新\(g(S)\),最后把合法状态的\(g\)进行枚举子集转移即可。

#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read(){
    T x=0,w=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*w;
}
template<class T> il T read(T&x){
    return x=read<T>();
}
using namespace std;
typedef long long LL;

co int N=10000+1,S=1<<8,INF=0x3f3f3f3f;
int n,m,D;
vector<int> to[N],we[N];
int f[S][N],g[S];
bool vis[N];
queue<int> q;
void spfa(int f[]){
    while(q.size()){
        int x=q.front();
        q.pop(),vis[x]=0;
        for(int i=0;i<(int)to[x].size();++i){
            int y=to[x][i],w=we[x][i];
            if(f[y]>f[x]+w){
                f[y]=f[x]+w;
                if(!vis[y]) q.push(y),vis[y]=1;
            }
        }
    }
}
il bool check(int s){
    return (s&((1<<D)-1))==(s>>D);
}
int main(){
    read(n),read(m),read(D);
    while(m--){
        int x=read<int>(),y=read<int>(),w=read<int>();
        to[x].push_back(y),we[x].push_back(w);
        to[y].push_back(x),we[y].push_back(w);
    }
    memset(f,0x3f,sizeof f),memset(g,0x3f,sizeof g);
    for(int i=1;i<=D;++i)
        f[1<<(i-1)][i]=f[1<<(D+i-1)][n-i+1]=0;
    int lim=(1<<(D<<1))-1;
    for(int i=0;i<=lim;++i){
        for(int j=1;j<=n;++j){
            for(int k=i&(i-1);k;k=(k-1)&i)
                f[i][j]=min(f[i][j],f[k][j]+f[i^k][j]);
            if(f[i][j]<INF) q.push(j),vis[j]=1;
        }
        spfa(f[i]);
        for(int j=1;j<=n;++j) g[i]=min(g[i],f[i][j]);
    }
    for(int i=0;i<=lim;++i)
        for(int t=(i-1)&i;t;t=(t-1)&i)
            if(check(t)&&check(i^t)) g[i]=min(g[i],g[t]+g[i^t]);
    printf("%d\n",g[lim]<INF?g[lim]:-1);
    return 0;
}

posted on 2019-08-20 21:33 autoint 阅读(...) 评论(...) 编辑 收藏

导航