NOIP 2009 Senior 4

来源:互联网 发布:java时间换算工具 编辑:程序博客网 时间:2024/06/06 01:40

思路 枚举
思路一:从上到下,从左到右逐个枚举
得分:75分

思路二:填数独的时候,一般的策略都是找可能的解尽量少的地方填,这里我们定义可能的解最少的那一格为最佳填数位置。考虑到要减少常数时间,我就将没有填数的格子找出来,根据能填数的多少排序,再挨个搜索。同时我也想到了最优解剪枝:假设接下的格子每个都能值90分,把他们加上都不足以达到目前已经有的最优解的话说明当前的填法不会有最优解。最后,在初始化的时候可以进行预判,如果给定的数据都非法就不用填了。

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <string>#include <iostream>#include <algorithm>#include <vector>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;inline int readIn(){    int a;    scanf("%d", &a);    return a;}const int maxn = 15;int rect[maxn][maxn];bool sX[maxn][maxn];bool sY[maxn][maxn];bool sArea[maxn][maxn];int filled;int base;int ans = -1;inline int calcScore(int x, int y, int num){    x = abs(x - 5);    y = abs(y - 5);    return (10 - std::max(x, y))*num;}inline int area_(int x, int y){    x--;    y--;    return x / 3 * 3 + y / 3;}struct Empty{    int x;    int y;    Empty(int x = 0, int y = 0) :x(x), y(y)    {    }    bool operator< (const Empty& b) const //注意排序函数的编写:如果排序函数写错了将会RE    {        int areaA = area_(x, y);        int areaB = area_(b.x, b.y);        int ansA = 0;        int ansB = 0;        for (int i = 1; i <= 9; i++)        {            if (!sX[x][i] && !sY[y][i] && !sArea[areaA][i]) ansA++;            if (!sX[b.x][i] && !sY[b.y][i] && !sArea[areaB][i]) ansB++;        }        return ansA < ansB;    }};std::vector<Empty> empties;void sudoku(int step = 0, int score = base){    if (90 * (81 - filled) + score <= ans) return; //最优性剪枝     if (filled == 81)    {        ans = std::max(ans, score);        return;    }    int x = empties[step].x, y = empties[step].y;    int area = area_(x, y);    for (int i = 1; i <= 9; i++)    {        if (!sX[x][i] && !sY[y][i] && !sArea[area][i])        {            rect[x][y] = i;            sX[x][i] = sY[y][i] = sArea[area][i] = true;            filled++;            sudoku(step+1, score + calcScore(x, y, i));            rect[x][y] = 0;            filled--;            sX[x][i] = sY[y][i] = sArea[area][i] = false;        }    }}int main(){    bool bOk = true;    for (int i = 1; i <= 9; i++)    {        for (int j = 1; j <= 9; j++)        {            int x = i;            int y = j;            int area = area_(x, y);            rect[x][y] = readIn();            int& t = rect[x][y];            if (t)            {                if (!sX[x][t] && !sY[y][t] && !sArea[area][t])                {                    sX[x][t] = true;                    sY[y][t] = true;                    sArea[area][t] = true;                    filled++;                    base += calcScore(x, y, t);                }                else                {                    bOk = false;                    break;                }            }        }        if (!bOk) break;    }    if (!bOk)    {        printf("%d\n", ans);        return 0;    }    for (int i = 1; i <= 9; i++)    {        for (int j = 1; j <= 9; j++)        {            if (!rect[i][j])            {                empties.push_back(Empty(i, j));            }        }    }    std::sort(empties.begin(), empties.end());    sudoku();    printf("%d\n", ans);    return 0;}

可惜的是,这样做仍然只有75分。仔细分析后发现:填写了第一个后,就不能保证后面填写的顺序是最优顺序了,最终导致跟没有优化差不多。

思路三:每一次搜索都寻找一次最佳填数位置。
之前没有这么做就是因为想到找到最佳填数位置这一操作可能耗时较多。但事实上这个担心是没有必要的:如果指数级的解答树能减去很大一部分,这一常数时间的花费是相当划算的。

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <string>#include <iostream>#include <algorithm>#include <vector>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;inline int readIn(){    int a;    scanf("%d", &a);    return a;}const int maxn = 15;int rect[maxn][maxn];bool sX[maxn][maxn];bool sY[maxn][maxn];bool sArea[maxn][maxn];int countX[maxn];int filled;int base;int ans = -1;inline int calcScore(int x, int y, int num) //其实我还想强调一下获取当前的圈数的方法{    x = abs(x - 5);    y = abs(y - 5);    return (10 - std::max(x, y))*num;}inline int area_(int x, int y){    x--;    y--;    return x / 3 * 3 + y / 3;}void sudoku(int score = base){    if (90 * (81 - filled) + score <= ans) return; //最优性剪枝     if (filled == 81)    {        ans = std::max(ans, score);        return;    }    int x = 0, y = 0;    int area = 0;    int maxVal = 0; //找到能填的数最少的那一个格子    for (int i = 1; i <= 9; i++)    {        if (countX[i] == 9) continue; //这一行都已经填满了,不用在这一行里找:尽量减少常数时间        for (int j = 1; j <= 9; j++)        {            if (rect[i][j]) continue;            int tempCount = 0;            int tempArea = area_(i, j);            for (int k = 1; k <= 9; k++)            {                if (!(!sX[i][k] && !sY[j][k] && !sArea[tempArea][k]))                {                    tempCount++;                }            }            if (tempCount == 9) return; //这个地方什么都不能填了,说明当前已经无解            if (tempCount > maxVal)            {                x = i;                y = j;                area = tempArea;                maxVal = tempCount;            }        }    }    for (int i = 1; i <= 9; i++)    {        if (!sX[x][i] && !sY[y][i] && !sArea[area][i])        {            rect[x][y] = i;            sX[x][i] = sY[y][i] = sArea[area][i] = true;            filled++;            countX[x]++;            sudoku(score + calcScore(x, y, i));            rect[x][y] = 0;            filled--;            countX[x]--;            sX[x][i] = sY[y][i] = sArea[area][i] = false;        }    }}int main(){    bool bOk = true;    for (int i = 1; i <= 9; i++)    {        for (int j = 1; j <= 9; j++)        {            int x = i;            int y = j;            int area = area_(x, y);            rect[x][y] = readIn();            int& t = rect[x][y];            if (t)            {                if (!sX[x][t] && !sY[y][t] && !sArea[area][t])                {                    sX[x][t] = true;                    sY[y][t] = true;                    sArea[area][t] = true;                    countX[x]++;                    filled++;                    base += calcScore(x, y, t);                }                else                {                    bOk = false;                    //进行预判,如果给定的数据都已经非法,则不要填数了                    break;                }            }        }        if (!bOk) break;    }    if(bOk) sudoku();    printf("%d\n", ans);    return 0;}

光说搜索也不是特别难,但写的时候顾虑太多了。所以建议还是先写出来再说。有些时候可能写出来优化后还要慢一些(比如思路二),而对拍又很难检测出来(合法数据太难编了)。所以要先建立理论,再坚定不移地编写代码,不要在开始写了过后犹豫。

原创粉丝点击