ZOJ1039

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=39

记忆化搜索。这个题也相当郁闷,开始用f[i][j]表示第 i 次选择时 j 是不是必胜数,如果选择 j 后没有必胜数,那么 j 就是必胜数,反之不是必胜数,用DFS(i, j)来判断,总是不对,郁闷死了。后来看题解,用二进制状态压缩,于是改成二进制状态压缩,搜索的方式也改成和题解一样的,只是对于除去非法数字的方法不同,但仍然是错,恶心。

#include<iostream>
#include<cstring>

using namespace std;

int f[1<<21];
bool g[1<<21];
bool used[25];

void Chenge(int i)
{
    int j = i;

    while (j <= 20)
    {
        used[j] = true;
        //for (int k=2; k<=20; k++)
        //    if (used[k] && k+j <=20)
        //        used[k+j] = true;
        j += i;
    }
}

void Back(bool tmp[])
{
    for (int i=0; i<25; i++)
        used[i] = tmp[i];
}

int work()
{
    int now = 0;

    for (int i=1; i<=20; i++)
        if (!used[i])
            now += 1<<i;
    return now;
}

int DFS()
{
    int now = work();
    if (g[now])
        return f[now];

    g[now] = true;
    f[now] = 0;
    bool tmp[25];
    for (int i=0; i<25; i++) tmp[i] = used[i];

    for (int i=2; i<=20; i++)
        if (!used[i])
    {
        Chenge(i);
        int New = work();
        int x = DFS();
        if (x == 0)
            f[now] += (1<<(i-1)) ;
        Back(tmp);
    }
    return f[now];
}

int main()
{
    int t;
    int k = 0;

    cin>>t;
    while (t--)
    {
        k++;
        memset(used,true,sizeof(used));
        memset(f,false,sizeof(f));
        memset(g,false,sizeof(g));
        int a;
        cin>>a;
        for (int i=0; i<a; i++)
        {
            int x;
            cin>>x;
            used[x] = false;
        }

        int ans = 0;
        cout<<"Scenario #"<<k<<":"<<endl;
        if (!(ans = DFS()))
        {
            cout<<"There is no winning move."<<endl;
            continue;
        }
        cout<<"The winning moves are:";
        for (int i=1; i<=20; i++)
        {
            if (ans % 2 == 1)
                cout<<' '<<i;
            ans >>= 1;
            if (ans == 0)
                break;
        }
        cout<<"."<<endl;
        cout<<endl;
    }
}

后来还是看题解。用二进制表示,从低位到高位,分别表示1到20是否在集合中。要判断一个数 j 是不是在集合中,只要&一下2^j就就可以了,结果位1,则在集合中,否则不在集合中。只是对于除去非法数字的方法没有看懂,郁闷。

#include<iostream>
#include<cstring>

using namespace std;

int f[1<<21];
int mask[1<<21];

int DFS(int num)
{
    if (f[num] == -1)
    {
        f[num] = 0;
        for (int i=2; i<=20; i++)
            if ((num & mask[i]) != 0)
        {
            int tmp = num;
            int j = i;
            while (j<=20)
            {
                int buf = (((num | 1) << j) | (mask[j]-1));
                tmp &= buf;
                j += i;
            }
            int x = DFS(tmp);
            if (x == 0)
                f[num] += mask[i];
        }
    }
    return f[num];
}

int main()
{
    int t;
    int k = 0;

    cin>>t;
    while (t--)
    {
        k++;
        memset(f,255,sizeof(f));
        for (int i=1; i<=20; i++)
            mask[i+1] = 1<<i;

        int a = 0,n;
        cin>>n;
        for (int i=0; i<n; i++)
        {
            int x;
            cin>>x;
            a |= mask[x];
        }

        int ans = DFS(a);
        cout<<"Scenario #"<<k<<":"<<endl;
        if (ans == 0)
        {
            cout<<"There is no winning move."<<endl;
            cout<<endl;
            continue;
        }
        cout<<"The winning moves are:";
        for (int i=1; i<=20; i++)
        {
            if (ans % 2 == 1)
                cout<<' '<<i;
            ans >>= 1;
            if (ans == 0)
                break;
        }
        cout<<"."<<endl;
        cout<<endl;
    }
}



你可能感兴趣的:(ZOJ1039)