HDU 3957 Street Fighter (最小支配集 DLX 重复覆盖+精确覆盖 )

DLX经典题型,被虐惨了……

建一个2*N行3*N列的矩阵,行代表选择,列代表约束。前2*N列代表每个人的哪种状态,后N列保证每个人至多选一次。

显然对手可以被战胜多次(重复覆盖),每个角色至多选择一次(精确覆盖)。

注意事项:

1.行数=∑每个人的模式数,之前我直接把行数当2*N了……但实际上也会有人只有一种模式的,也就是说实际行数小于等于2*N

2.建图的时候注意:这个人不光能覆盖他所战胜的某角色的某模式,还覆盖了他自己的所有模式(因为他不用战胜自己)。之前没注意这个问题,样例全成无解了orz……

3.处理精确覆盖和重复覆盖的先后顺序。如果优先处理精确覆盖,会把重复覆盖的一些行也删掉,这样前面可以重复覆盖的很多列也被当成了精确覆盖,显然不对了。所以应当先处理重复覆盖。恢复的时候遵循先删除的后恢复,后删除的先恢复

4.只要满足重复覆盖的条件即为一个可行解。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;

const int MAXN = 50;
const int INF = 1 << 30;

int N;
int U[ (2*MAXN)*(3*MAXN) ], D[ (2*MAXN)*(3*MAXN) ];
int L[ (2*MAXN)*(3*MAXN) ], R[ (2*MAXN)*(3*MAXN) ];
int C[ (2*MAXN)*(3*MAXN) ];
int cnt[ 3*MAXN ];
bool mx[ 2*MAXN ][ 3*MAXN ];
bool vis[ 3*MAXN ];
bool vs[MAXN][2][MAXN][2];   //i的x模式能打败j的y模式
int modelN[MAXN];            //i有几个模式
int sum[MAXN];
int head;
int maxr, maxc;

void Remove( int c )    //重复覆盖删除列
{
    for ( int i = D[c]; i != c; i = D[i] )
    {
        R[ L[i] ] = R[i];
        L[ R[i] ] = L[i];
    }
    return;
}

void Resume( int c )    //重复覆盖恢复列
{
    for ( int i = D[c]; i != c; i = D[i] )
    {
        R[ L[i] ] = i;
        L[ R[i] ] = i;
    }
    return;
}

void ExRemove( int c )    //精确覆盖删除列+行
{
    int i, j;
    L[ R[c] ] = L[c];
    R[ L[c] ] = R[c];
    for ( i = D[c]; i != c; i = D[i] )
    {
        for ( j = R[i]; j != i; j = R[j] )
        {
            U[ D[j] ] = U[j];
            D[ U[j] ] = D[j];
            --cnt[ C[j] ];
        }
    }
    return;
}

void ExResume( int c )      //精确覆盖恢复列+行
{
    int i, j;
    R[ L[c] ] = c;
    L[ R[c] ] = c;
    for ( i = D[c]; i != c; i = D[i] )
    {
        for ( j = R[i]; j != i; j = R[j] )
        {
            U[ D[j] ] = j;
            D[ U[j] ] = j;
            ++cnt[ C[j] ];
        }
    }
    return;
}

bool build()
{
    head = 0;
    for ( int i = 0; i < maxc; ++i )
    {
        R[i] = i + 1;
        L[i + 1] = i;
    }
    R[maxc] = 0;
    L[0] = maxc;

    //列链表
    for ( int j = 1; j <= maxc; ++j )
    {
        int pre = j;
        cnt[j] = 0;
        for ( int i = 1; i <= maxr; ++i )
        {
            if ( mx[i][j] )
            {
                ++cnt[j];
                int cur = i * maxc + j;
                U[cur] = pre;
                D[pre] = cur;
                C[cur] = j;
                pre = cur;
            }
        }
        U[j] = pre;
        D[pre] = j;
        //if ( !cnt[j] ) return false;
    }

    //行链表
    for ( int i = 1; i <= maxr; ++i )
    {
        int pre = -1, first = -1;
        for ( int j = 1; j <= maxc; ++j )
        {
            if ( mx[i][j] )
            {
                int cur = i * maxc + j;
                if ( pre == -1 ) first = cur;
                else
                {
                    L[cur] = pre;
                    R[pre] = cur;
                }
                pre = cur;
            }
        }
        if ( first != -1 )
        {
            R[pre] = first;
            L[first] = pre;
        }
    }

    return true;
}

/****************以上DLX模板****************/

//估价函数:至少还要选几个人
int h()
{
    memset( vis, false, sizeof(vis) );
    int res = 0;
    for ( int c = R[head]; c <= maxr && c != head; c = R[c] )
    {
        if ( !vis[c] )
        {
            ++res;
            vis[c] = true;
            for ( int i = D[c]; i != c; i = D[i] )
                for ( int j = R[i]; j != i; j = R[j] )
                    vis[ C[j] ] = true;
        }
    }
    return res;
}

bool DFS( int dep, int limit )
{
    //A-star剪枝
    if ( dep + h() > limit ) return false;

    //只要前面满足重复覆盖的条件,即为可行解
    if ( R[head] > maxr || R[head] == head ) return true;

    int c, minv = INF;
    for ( int i = R[head]; i <= maxr && i != head; i = R[i] )
    {
        if ( cnt[i] < minv )
        {
            minv = cnt[i];
            c = i;
        }
    }

    for ( int i = D[c]; i != c; i = D[i] )
    {
        Remove(i);
        //注意处理重复覆盖和精确覆盖的顺序
        for ( int j = R[i]; j != i; j = R[j] )
            if ( C[j] <= maxr ) Remove(j);

        for ( int j = R[i]; j != i; j = R[j] )
            if ( C[j] > maxr ) ExRemove( C[j] );

        if ( DFS( dep + 1, limit ) )
        {
            //注意恢复精确覆盖和重复覆盖的顺序,这样恢复之后可以不必重新建图
            for ( int j = R[i]; j != i; j = R[j] )
                if ( C[j] > maxr ) ExResume( C[j] );

            for ( int j = R[i]; j != i; j = R[j] )
                if ( C[j] <= maxr ) Resume(j);

            Resume(i);  //之前忘了恢复i,死活TLE
            return true;
        }

        for ( int j = R[i]; j != i; j = R[j] )
            if ( C[j] > maxr ) ExResume( C[j] );
        for ( int j = R[i]; j != i; j = R[j] )
            if ( C[j] <= maxr ) Resume(j);
        Resume(i);
    }

    return false;
}

int solved()
{
    int l = 0, r = N;
    int ans;

    while ( l <= r )
    {
        int mid = ( l + r ) >> 1;
        if ( DFS( 0, mid ) )
        {
            r = mid - 1;
            ans = mid;
        }
        else l = mid + 1;
    }

    return ans;
}

void show()
{
    for ( int i = 0; i <= maxr; ++i )
    {
        for ( int j = 0; j <= maxc; ++j )
            printf( "%d", mx[i][j] );
        puts("");
    }
}

void init()
{
    memset( mx, false, sizeof(mx) );

    for( int i = 0; i < N; ++i )
    {
        for ( int x = 0; x < modelN[i]; ++x )
        {
            mx[ sum[i] + x ][ maxr + i + 1 ] = true;
            mx[ sum[i] + x ][ sum[i] ] = true;
            if ( modelN[i] > 1 )
            {
                mx[ sum[i] + x ][ sum[i] + 1 ] = true;
            }
            for ( int j = 0; j < N; ++j )
            {
                for ( int y = 0; y < modelN[j]; ++y )
                {
                    if ( vs[i][x][j][y] )
                    {
                        mx[ sum[i] + x ][ sum[j] + y ] = true;
                    }
                }
            }
        }
    }

    //show();
    return;
}

int main()
{
    //freopen( "in.txt", "r", stdin );
    //freopen( "out.txt", "w", stdout );
    int T, cas = 0;
    scanf( "%d", &T );
    while ( T-- )
    {
        memset( vs, false, sizeof(vs) );
        scanf( "%d", &N );
        maxr = 0;

        for ( int i = 0; i < N; ++i )
        {
            scanf( "%d", &modelN[i] );
            sum[i] = maxr + 1;
            for ( int j = 0; j < modelN[i]; ++j )
            {
                ++maxr;
                int K;
                scanf( "%d", &K );
                for ( int k = 0; k < K; ++k )
                {
                    int id, mode;
                    scanf( "%d%d", &id, &mode );
                    vs[i][j][id][mode] = true;
                }
            }
        }

        maxc = maxr + N;
        init();
        build();
        printf("Case %d: %d
", ++cas, solved() );
    }
    return 0;
}