[NOI.AC]【CSP2019模拟 Day 2】c(魔鬼暴搜+状态压缩+打表)

题目

Description
俄罗斯方块是一个四连通的,由 1 × 1 1\times 1 1×1的小正方形构成的方块,并且可以被 3 × 3 3\times 3 3×3的正方形装下,比如:

...  @@@  @@@  .@@
...  @@@  @.@  @.@
.@.  @@@  @..  @@@

而这些就是不合法的:

@@.  @.@  .@@.
...  .@.  @@@@
.@@  @.@  .@@.

其中@表示方块,.表示空格。
如果两个方块经过旋转,翻转,平移能变得一样,就认为它们本质相同。
如果你愿意去打个表,可以发现本质不同的方块只有 35 35 35种。
这个游戏的目的,就是给一个 8 × 8 8×8 8×8的棋盘,用某一种俄罗斯方块填成目标状态。
比如,这是一个目标状态对于两种俄罗斯方块的不同填法:

....11..  ....11..
...221..  ...221..
...211..  ...321..
...22...  ...32...
.333....  .433....
4343....  5444....
444.....  555.....
........  ........

对于任意两种不同的俄罗斯方块,你需要求出是否一个 8 × 8 8×8 8×8的非空目标状态,使得它能被两种方块分别填充。

Input
第一行一个整数 T = 595 = 35 × ( 35 − 1 ) 2 T=595=\frac{35\times (35-1)}{2} T=595=235×(351)表示数据组数。
接下来 T T T组数据,每组数据由三行七列的字符串构成,第四列是空格,前三列和后三列表示一种方块。

Output
对于每组数据,如果存在一种构造,输出POSSIBLE并给出一组构造,否则输出IMPOSSIBLE
你的构造可以使用数字,大小写字母,用不同的字符表示不同的方块,可以证明如果有解,则存在一种构造不会使用超过 62 62 62种不同的方块。

Sample Input

2
.@@ .@.
.@. .@.
.@@ @@.
@@@ @@@
@.@ @@@
@@@ @@@

Sample Output

POSSIBLE
....11.. ....11..
...221.. ...221..
...211.. ...321..
...22... ...32...
.333.... .433....
4343.... 5444....
444..... 555.....
........ ........
IMPOSSIBLE

Constraint
本题代码长度限制 256 K B 256KB 256KB
如果你回答对了 S S S组数据,你的得分为 ⌊ 100 × ( S T ) 2 ⌋ \lfloor 100\times (\frac{S}{T})^2\rfloor 100×(TS)2
输出数据需要严格按照输出格式,不然可能会直接记为 0 0 0分。如果对于一组数据你无法判断是否有解或是无法构造解,建议输出IMPOSSIBLE来保证获得其他测试点的分。

分析

破题

我说的就是破题!不是破题。
题解:
题解
(我的)代码:
代码


分三步:

  • 打出拼图块的表;
  • 拼图块两两配对,状压搜索找答案;
  • 运行10分钟左右出解,然后粘进程序,提交。打表运行时间

思(dai)路(ma)

打拼图块

考虑用9位二进制数压缩状态,直接枚举,判重。

判重时参考标程,利用GetWays函数找到这个拼图块的所有可能情况(旋转,平移,对称等)并排序,这样构成了这个拼图块的一个等价类。对于每个等价类,它的标识就是状态压缩过后最小的那个(所以要排序+去重),这样方便判重。

注意Move函数(将拼图块对齐到左上角)的神仙写法(我之前是模拟+暴力,看了标程才知道自己是憨憨),wxhtxdy!RotateReflect函数模拟即可。当然还有判断4连通,dfs暴力即可。

打拼图

对于拼图块ij(二进制状压),跑dfs,这里又膜一下大佬的神仙写法:

  • 地图是 8 × 8 = 64 8\times8=64 8×8=64个格子,刚好用usigned long long状压;
  • 一个状态被表示为一个五元组dfs(Board,Board0,Board1,x,y),其中前三个是usigned long long(状压),后两个是坐标。
    • Board:可以理解为Board0|Board1,决定了当前状态下是放i还是放j(例如Board上某一格为1,而Board0的对应格不是,那肯定要放i);
    • Board0Board1:分别表示i拼出的形状和j拼出的形状;
    • xy:当前坐标。
  • 我们定义:把拼图块i放到地图的(x,y)处,是指,让i'的最下端的最右端与格子(x,y)重合(i'i中为1的部分,当然,要先用Move函数对齐),这就是Put函数。例如:

对齐的方式
因此这样放是不正确的(不能拿i去跟(x,y)配对):
错误的对齐方式
我之前就是用第二种方法写,这样会漏情况!!!

  • 找到了一样的就用Save函数存下来,返回即可。

细节

注意开始打之前要把格式弄好!!!不然打出来用不了又要重新打!!!!
打的时候可以时不时地打开table.txt!!!!看看有没有问题!!!!

不要在main()外面赋值,会编译超时(其实是根本编译不出来)!!!

C++11真好用!!!

代码

表就不展示了,,,,,,,

#include<bits/stdc++.h>
using namespace std;

#define NO {flag=0;break;}
#define ULL unsigned long long

vector<int> Blocks;
bool vis[(1<<9)-1];
const int Bit[5][5]={{0},{0,1<<8,1<<7,1<<6},{0,1<<5,1<<4,1<<3},{0,1<<2,1<<1,1<<0}};
const string Alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$";

int Move(int S){//wxhtxdy !!!!!!!!!! 不说了, 真的巧妙
    while(!(S&448)) S<<=3;
    //448二进制: 111 000 000
    while(!(S&292)) S<<=1;
    //292二进制: 100 100 100
    return S;
}
int Now;
void dfs(int i,int j){
    if(i<0||j<0||i>3||j>3||!(Now&Bit[i][j]))
        return;
    Now^=Bit[i][j];
    dfs(i+1,j),dfs(i,j+1),
    dfs(i-1,j),dfs(i,j-1);
}
bool isConnected(int S){
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(S&Bit[i][j]){
                Now=S;
                dfs(i,j);
                return !Now;
            }
    return 0;
}
int Rotate(int S){
    int ret=0;
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(S&Bit[i][j])
                ret|=Bit[j][4-i];
    return ret;
}
int Reflect(int S){
    int ret=0;
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(S&Bit[i][j])
                ret|=Bit[4-i][j];
    return ret;
}
void Print(int S){
    for(int i=1;i<=3;i++,puts(""))
        for(int j=1;j<=3;j++)
            putchar((S&Bit[i][j])?'@':'.');
}
vector<int> GetWays(int S){
    vector<int> ret;
    for(int t1=1;t1<=2;t1++,S=Reflect(S))
        for(int t2=1,i=Move(S);t2<=4;t2++,i=Rotate(i)){
            int j=i;
            while(1){
                int k=j;
                while(1){
                    ret.push_back(k);
                    if(k&73) break;
                    //73二进制: 001 001 001
                    k>>=1;
                }
                if(j&7) break;
                //7二进制: 000 000 001
                j>>=3;
            }
        }
    sort(ret.begin(),ret.end());
    ret.erase(unique(ret.begin(),ret.end()),ret.end());
    return ret;
}
void GetBlocks(){
    int Full=(1<<9)-1;
    for(int i=0;i<=Full;i++){
        if(!isConnected(i))
            continue;
        vector<int> tmp=GetWays(i);
        if(!vis[tmp[0]])//tmp[0]作为此方案的标识
            vis[tmp[0]]=1,Blocks.push_back(i);
    }
}

#define Pos(i,j) (1ull<<(((7-(i))<<3)+(8-(j))))

int Sign0,Sign1;
vector<int> Ways0,Ways1;
vector<string> Res0,Res1;
set<tuple<ULL,ULL,ULL,int,int> > Ban;
ULL Put(int x,int y,int S){
    S=Move(S);
    int Low=8;
    while(!(S&(1<<Low)))
        Low--;
    x-=(8-Low)/3,y-=(8-Low)%3;//找到最下方的最右端, 转换参考系
    ULL ret=0;
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(S&Bit[i][j]){
                if(x+i-1<0||y+j-1<0||x+i-1>=8||y+j-1>=8)
                    return 0;
                ret|=Pos(x+i-1,y+j-1);
            }
    return ret;
}
void Save(int id,int x,int y,int S){
    S=Move(S);
    int Low=8;
    while(!(S&(1<<Low)))
        Low--;
    x-=(8-Low)/3,y-=(8-Low)%3;
    int &P=id?Sign1:Sign0;
    vector<string> &T=id?Res1:Res0;
    for(int i=1;i<=3;i++)
        for(int j=1;j<=3;j++)
            if(S&Bit[i][j])
                T[x+i-1][y+j-1]=Alphabet[P];
    ++P;
};
bool dfs(ULL Board,ULL Board0,ULL Board1,int x,int y){
    if(Ban.count(make_tuple(Board,Board0,Board1,x,y)))
        return 0;
    if(Board0&&Board0==Board1)
        return 1;
    Ban.emplace(Board,Board0,Board1,x,y);
    if(x>7)
        return 0;
    if(y>7)
        return dfs(Board,Board0,Board1,x+1,0);
    if(!(Board&Pos(x,y)))
        return dfs(Board|Pos(x,y),Board0,Board1,x,y)||dfs(Board,Board0,Board1,x,y+1);
    if(!(Board0&Pos(x,y))){
        for(auto i:Ways0){
            ULL tmp=Put(x,y,i);
            if(!tmp||(Board0&tmp))
                continue;
            if(dfs(Board|tmp,Board0|tmp,Board1,x,y)){
                Save(0,x,y,i);
                return 1;
            }
        }
        return 0;
    }
    if(!(Board1&Pos(x,y))){
        for(auto i:Ways1){
            ULL tmp=Put(x,y,i);
            if(!tmp||(Board1&tmp))
                continue;
            if(dfs(Board|tmp,Board0,Board1|tmp,x,y)){
                Save(1,x,y,i);
                return 1;
            }
        }
        return 0;
    }
    return dfs(Board,Board0,Board1,x,y+1);
}
void Solve(int x,int y){
    puts("/*");
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++)
            putchar(x&Bit[i][j]?'@':'.');
        putchar(' ');
        for(int j=1;j<=3;j++)
            putchar(y&Bit[i][j]?'@':'.');
        putchar('\n');
    }
    puts("*/");
    Ban.clear();
    Sign0=0,Sign1=0;
    Ways0=GetWays(x),Ways1=GetWays(y);
    Res0.clear(),Res0.resize(8,"........");
    Res1.clear(),Res1.resize(8,"........");
    if(dfs(0,0,0,0,0)){
        printf("Ans[%d][%d]=",x,y);
        printf("\"POSSIBLE\\n\"\n");
        for(int i=0;i<7;i++)
            cout<<"\""<<Res0[i]<<" "<<Res1[i]<<"\\n\"\n";
        cout<<"\""<<Res0[7]<<" "<<Res1[7]<<"\\n\";\n";
        printf("Ans[%d][%d]=",y,x);
        printf("\"POSSIBLE\\n\"\n");
        for(int i=0;i<7;i++)
            cout<<"\""<<Res1[i]<<" "<<Res0[i]<<"\\n\"\n";
        cout<<"\""<<Res1[7]<<" "<<Res0[7]<<"\\n\";\n";
    }
    else{
        printf("Ans[%d][%d]=",x,y),puts("\"IMPOSSIBLE\\n\";");
        printf("Ans[%d][%d]=",y,x),puts("\"IMPOSSIBLE\\n\";");
    }
    puts("");
}

void GetTable(){
    freopen("table.txt","w",stdout);
    GetBlocks();
    for(auto i:Blocks){
        for(auto j:Blocks){
            if(j<=i)
                continue;
            Solve(i,j);
        }
    }
}

int main(){
    ios::sync_with_stdio(0);
	vector<vector<string> > Ans(512,vector<string>(512));
    /*此处省略一万行的表*/
    /*调用GetTable(), 然后把table.txt里面的粘到这里即可*/
    /*大约需要11分钟, 请耐心等待*/
    int T;
    scanf("%d",&T);
    while(T--){
        int x=0,y=0;
        for(int i=1;i<=3;i++){
            char str0[5],str1[5];
            scanf("%s%s",str0+1,str1+1);
            for(int j=1;j<=3;j++){
                if(str0[j]=='@')
                    x|=Bit[i][j];
                if(str1[j]=='@')
                    y|=Bit[i][j];
            }
        }
        x=GetWays(x)[0],y=GetWays(y)[0];
        cout<<Ans[x][y];
    }
}
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值