HDU 5727 Necklace(玄学+状压DP 或 匈牙利)

[题目链接]

[题意]
2n颗宝石,n颗为阴,n颗为阳,阴阳交错的组成一个圈(圆排列)
给定一系列阴阳宝石对,如果在排列中这两颗宝石相邻,阳宝石就会失去能量
求如何排列能让最少的阳宝石失去能量,输出最少的宝石数目

[分析]
n比较小( n<=9 ),自然想到状压
但如果交错的枚举阴阳宝石,状态不好表示,也不好转移
于是想到先固定阴宝石的排列,再状压dp求阳宝石的最优放法
朴素做法 O(n!*2^n) TLE
xjb优化:
枚举排列时,用random_shuffle随机枚举,并加入最优性剪枝,调整了几次枚举次数后,就玄学的过掉了(而且跑得出奇的快)…

[代码]

#include <bits/stdc++.h>
using namespace std ;
const int N = 9 ;
const int inf = 0x3f3f3f3f ;
typedef long long LL ;

int T , n , m ;
int g[N][N] ;
int b[N] ;
int f[1<<N] ;
int L[1<<N] ;
int ans ;

int dp()
{
    memset(f,inf,sizeof(f)) ;
    f[0] = 0 ;
    for( int s = 0 ; s < 1<<n ; s++ )
    {
        if( f[s] >= ans ) continue ;
        int k = L[s] , kk = (L[s]+1)%n ;
        for( int i = 0 ; i < n ; i++ )
        {
            if( s&(1<<i) ) continue ;
            int ss = s|(1<<i) ;
            f[ss] = min(f[ss],f[s]+(g[i][b[k]]|g[i][b[kk]])) ;
        }
    }
    return f[(1<<n)-1] ;
}

int main()
{
    for( int i = 1 ; i < 1<<N ; i++ )
        L[i] = L[i-(i&-i)] + 1 ;
    while( ~scanf( "%d%d" , &n , &m ) )
    {
        memset(g,0,sizeof(g)) ;
        while( m-- )
        {
            int u , v ;
            scanf( "%d%d" , &u , &v ) ;
            u-- ; v-- ;
            g[u][v] = 1 ;
        }
        if( n == 0 || m == 0 )
        {
            puts("0") ;
            continue ;
        }
        for( int i = 0 ; i < n ; i++ )
            b[i] = i ;
        ans = inf ;
        for( int i = 0 ; i < 1<<(n+1) ; i++ )
        {
            random_shuffle(b,b+n) ;
            ans = min(ans,dp()) ;
        }
        printf( "%d\n" , ans ) ;
    }
    return 0 ;
}

[更新]
经菊苣们指导,枚举阴后,放阳可以构造二分图,跑匈牙利,要快很多
具体做法是,确定阴的排列后,若某个阳宝石放在某个位子上不会失去能量,则把阳宝石与该位子连边
答案为 n-最大匹配
这样朴素枚举圆排列就能过了,加上玄学随机后更是跑到了15ms…不知道是不是数据太水…

[代码]

#include <bits/stdc++.h>
using namespace std ;
const int N = 9 ;
const int inf = 0x3f3f3f3f ;
typedef long long LL ;

int T , n , m ;
int g[N][N] , b[N] ;
int link[N*2+5] , vis[N*2+5] ;
int ans ;
vector<int> G[N*2] ;

bool dfs( int u )
{
    for( int v : G[u] )
    {
        if( vis[v] ) continue ;
        vis[v] = 1 ;
        if( link[v] == -1 || dfs(link[v]) )
        {
            link[v] = u ;
            return 1 ;
        }
    }
    return 0 ;
}

int hungury()
{
    for( int i = 0 ; i < n<<1 ; i++ ) G[i].clear() ;
    for( int i = 0 ; i < n ; i++ )
        for( int j = 0 ; j < n ; j++ )
            if( !g[i][b[j]] && !g[i][b[(j+1)%n]] )
                G[i].push_back(j+n) , G[j+n].push_back(i) ;
    int cnt = 0 ;
    memset(link,-1,sizeof(link)) ;
    for( int i = 0 ; i < n ; i++ )
    {
        memset(vis,0,sizeof(vis)) ;
        cnt += dfs(i) ;
    }
    return cnt ;
}

int main()
{
    while( ~scanf( "%d%d" , &n , &m ) )
    {
        memset(g,0,sizeof(g)) ;
        while( m-- )
        {
            int u , v ;
            scanf( "%d%d" , &u , &v ) ;
            u-- ; v-- ;
            g[u][v] = 1 ;
        }
        if( n == 0 || m == 0 )
        {
            puts("0") ;
            continue ;
        }
        for( int i = 0 ; i < n ; i++ )
            b[i] = i ;
        ans = inf ;
        do
        {
            ans = min(ans,n-hungury()) ;
        }while( next_permutation(b,b+n) && !b[0] ) ;
        /* 玄学随机
        for( int i = 0 ; i < 1<<(n+1) ; i++ )
        {
            random_shuffle(b,b+n) ;
            ans = min(ans,n-hungury()) ;
        }*/
        printf( "%d\n" , ans ) ;
    }
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值