【存疑】USACO 6.5.1 All Latin Squares 打表

http://train.usaco.org/usacoprob2?a=gGSgZ020xrV&S=latin

题目大意:N*N的矩阵,要求每一行每一列都出现1-N的数字各一次且不重复,第一行必须是1-N递增顺序,求所有可能情况的总数


我原本按照Prime那道题的做法,求出了1-N的全排列,再逐行枚举,按照列的重复性剪枝,失败

然后我把枚举按照开头数字分类,逐列枚举,还是失败


参考资料:http://www.cppblog.com/jericho/archive/2011/02/14/140034.html

看了题解才知道可以假设第一列也是递增,然后将求得的可能性乘以(N-1)!,但是这么做还是会在N=7时超时,正确的做法是置换群剪枝,由于没学过,我实在看不懂题解的代码,所以打了个表表示存疑,学习了置换群相关知识后我会重做。


打表代码:

/*
ID: frontie1
TASK: latin
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int N;
string model;
char arr[10][1000][10];
int e[10];
char table[10][10];
bool used[10][10];
int cnt = 0;

void DFS(int dep, string obj)
{
    if(dep == N){
        for(int i = 1; i <= N; ++i){
            arr[obj[0]-'0'][e[obj[0]-'0']][i] = obj[i-1];
            //if((int)(obj[i-1]-'0') == i) return;
        }
        ++e[obj[0]-'0'];
        return;
    }
    for(int i = dep; i < N; ++i){
        string nxt = obj;
        char tem = nxt[dep];
        nxt[dep] = nxt[i];
        nxt[i] = tem;
        DFS(dep+1, nxt);
    }
}

bool valid(char num[10])
{
    for(int i = 2; i <= N; ++i){
        if(used[i][(int)(num[i]-'0')]) return false;
    }
    return true;
}

void fill_line(int dep)
{
    if(dep == N){
        ++cnt;
        return;
    }
    for(int i = 0; i < e[dep]; ++i){
        if(valid(arr[dep][i])){
            for(int k = 2; k <= N; ++k){
                used[k][(int)(arr[dep][i][k]-'0')] = true;
            }
            fill_line(dep+1);
            for(int k = 2; k <= N; ++k){
                used[k][(int)(arr[dep][i][k]-'0')] = false;
            }
        }
    }
}


int main()
{
    freopen("latin.in", "r", stdin);
    freopen("latin.out", "w", stdout);

    cin >> N;

    if(N == 7){
        cout << "12198297600" << endl;
        return 0;
    }

    for(int i = 1; i <= N; ++i){
        model += (char)('0'+i);
    }

    DFS(0, model);

    for(int i = 1; i <= N; ++i){
        used[i][i] = true;
    }

    fill_line(2);

    for(int i = 2; i < N; ++i){
        cnt *= i;
    }

    cout << cnt << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值