[题解] luogu P1985 [USACO07OPEN]翻转棋

题面
今天学搜索,正好水一发以前做的这道毒瘤题

话说这道题做了我一天,别人都是各种优化,不超100行

天真的我硬核刚了220行,全程0优化水过

但其实不用这么长,我有的函数写的有点重复了(

思路:

显然是dfs,一行一行的来

搜到[i, j]时(i > 1),看[i - 1, j]是否为黑,是的话就翻转[i, j],

也就是说搜完当前行就要保证上一行的棋全都翻成了白色

当搜到最后一行时,

既要保证上一行翻成白色,还要保证自己也都翻成白色,

最后还要特判一下最后两个的翻转.

当时年少轻狂,我想着层次一定要清晰,
于是就SB地分别打了四个函数:

  1. 特判第一行,firstLineS
  2. 当只有一列的时候,dfs1
  3. 当搜到最后一行时或本来只有一行时,dfsn
  4. 正常搜索,dfs
这个题有个坑,92分卡了一下午: 搜到的第一个解不能直接输出,它是最优解,但不一定是字典序最小的,所以继续搜完

[collapse title="Code" status="false"]

#include <bits/stdc++.h>

using namespace std;

int n, m, a, minans = 99999;
int Chess[25][25], ans[25][25], minan[25][25];

void c(int i, int j)
{
    if(Chess[i][j]%2 == 0) Chess[i][j] = 1;
    else Chess[i][j] = 0;
}
void print()
{
    a = 0;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            if (Chess[i][j] % 2 == 1) return ;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            a += ans[i][j] % 2;
    if(minans > a)
    {
        minans = a;
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                minan[i][j] = ans[i][j] % 2;
    }
}
int back1(int p)
{
    if ( Chess[m][p] == 1 && p == n)
        return 1;
    else if (p != n)
        return 1;
    return 0;
}
int back2(int p)
{
    if ( Chess[m][p] == 0 && p == n)
        return 0;
    else if (p != n)
        return 0;
    return 1;
}
void dfsn(int num) {
    if (num == n + 1) {
        print();
        return ;
    }
    int line = m;
    if (m == 1)
    {
        if (num == 1)
        {
            dfsn(num + 1);
            c(line, num + 1);
            c(line, num);
            ans[line][num]++;
            dfsn(num + 1);
        }
        if (Chess[line][num - 1]==0 && back2(num)==0)
            dfsn(num+1);
        else if (Chess[line][num - 1]==1 && back1(num)==1)
        {
            c(line, num - 1);
            c(line, num + 1);
            c(line, num);
            ans[line][num]++;

            dfsn(num + 1);
        }
    }
    else if (num == 1)
    {
        if (Chess[m - 1][1] == 0)
            dfsn(2);    

        else
        {
            c(m - 1 , 1);
            c(m, 2);
            c(m, 1);
            ans[m][1]++;

            dfsn(2);
        }
    }

    else if (Chess[line - 1][num]==1 && Chess[line][num - 1]==1 && back1(num)==1)
    {
        c(line - 1 , num);
        c(line, num - 1);
        c(line, num + 1);
        c(line, num);
        ans[line][num]++;

        dfsn(num + 1);
        /*
        ans[line][num]--;
        c(line - 1, num);
        c(line, num - 1);
        c(line, num + 1);
        c(line, num);
        */
    }
    else if (Chess[line - 1][num] == 0 && Chess[line - 1][num] == 0 && back2(num) == 0)
        dfsn(num + 1);
}
void dfs(int line, int num)
{
    if (num == n + 1 && line == m - 1)
    {
        dfsn(1);
        return ;
    }
    if (num == n + 1)
    {
        dfs(line + 1, 1);
        return ;
    }
    if (Chess[line - 1][num] == 0)
        dfs(line, num + 1);
    else
    {
        c(line - 1, num);
        c(line + 1, num);
        c(line, num - 1);
        c(line, num + 1);
        c(line, num);
        ans[line][num]++;
        dfs(line, num + 1);
 /*
        ans[line][num]--;
        c(line - 1, num);
        c(line + 1, num);
        c(line, num - 1);
        c(line, num + 1);
        c(line, num);
*/
    }
}
void firstLineS(int k)
{
    if (k == n + 1)
    {
        if (m > 2)
            dfs(2, 1);
        else if (m == 2)
            dfsn(1);
        return ;
    }
    firstLineS(k + 1);
    c(1, k);
    c(1, k - 1);
    c(1, k + 1);
    c(2, k);
    ans[1][k]++;
    firstLineS(k + 1);
    ans[1][k]--;
    c(1, k);
    c(1, k - 1);
    c(1, k + 1);
    c(2, k);
}
void dfs1(int num)
{
    if (num == m + 1)
    {
        print();
        return ;
    }
    if (num == 1)
    {
        dfs1(num + 1);
        ans[num][1]++;
        c(num, 1);
        c(num + 1, 1);
        dfs1(num + 1);
        ans[num][1]--;
        c(num, 1);
        c(num + 1, 1);
    }
    if(Chess[num - 1][1] == 0)
        dfs1(num + 1);
    else  {
        ans[num][1]++;
        c(num, 1);
        c(num + 1, 1);
        c(num - 1, 1);

        dfs1(num + 1); 

        ans[num][1]--;
        c(num + 1, 1);
        c(num - 1, 1);
        c(num, 1);
    }
}

int main()
{
    cin >> m>> n;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            cin >> Chess[i][j];
    if (m == 1) dfsn(1);
    if (n == 1) dfs1(1);
    firstLineS(1);
    if (minans==99999)
        cout<<"IMPOSSIBLE"<<endl;
    else
        for (int i = 1; i <= m; i++)
        {
            for (int j = 1; j <= n; j++)
                cout << minan[i][j]%2<<" ";
            cout << endl;
        }
    return 0;
}

[/collapse]

原文地址:https://www.cnblogs.com/martixx/p/11232438.html

时间: 2024-08-02 16:58:52

[题解] luogu P1985 [USACO07OPEN]翻转棋的相关文章

P1985 [USACO07OPEN]翻转棋

题目链接: 翻转棋 题目分析: 先状压/\(dfs\)枚举第一排状态,然后在每个\(1\)下面翻,即确定了第一排就确定了后面的状态 最后验证一下最后一排是不是全0即可 代码: #include<bits/stdc++.h> #define N 50 using namespace std; inline int read() { int cnt = 0, f = 1; char c = getchar(); while (!isdigit(c)) {if (c == '-') f = -f;

P1985 [USACO07OPEN]翻转棋 题解

CSDN同步 原题链接 简要题意: 给定一个 \(01\) 棋盘,每次可以翻转一个"十"字形(即一个格子连同它四方向的相邻格子,出界则不翻).求在哪些格子上翻转(十字形的中心)可以使得 翻转后全 \(0\) 且 方案字典序最小 . 首先 \(n,m \leq 15\),本着面向数据范围做题的原理,分析算法. 算法一 枚举翻转哪些格子进行验证. 时间复杂度:\(O(2^{n \times m} \times n \times m)\),难以接受这样的爆炸性复杂度. 算法二 需要我们分析一

题解 luogu P1850 【换教室】

题解 luogu P1850 [换教室] 时间:2019.8.6 一晚上(约 3.5h 写完) 题目描述 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 \(2n\) 节课程安排在 \(n\) 个时间段上.在第 \(i\)(\(1 \leq i \leq n\))个时间段上,两节内容相同的课程同时在不同的地点进行,其中,牛牛预先被安排在教室 \(c_i\) 上课,而另一节课程在教室 \(d_i\) 进行. 在不提交任何申请的情况下,学生们需要

题解 luogu P5021 【赛道修建】

题解 luogu P5021 [赛道修建] 时间:2019.8.9 20:40 时间:2019.8.12 题目描述 C 城将要举办一系列的赛车比赛.在比赛前,需要在城内修建 \(m\) 条赛道. C 城一共有 \(n\) 个路口,这些路口编号为 \(1,2,\dots,n\),有 \(n-1\) 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口.其中,第 \(i\) 条道路连接的两个路口编号为 \(a_i\) 和 \(b_i\),该道路的长度为 \(l_i\).借助这 \(n-1\) 条

luogu P1146 硬币翻转

题目描述 在桌面上有一排硬币,共N枚,每一枚硬币均为正面朝上.现在要把所有的硬币翻转成反面朝上,规则是每次可翻转任意N-1枚硬币(正面向上的被翻转为反面向上,反之亦然).求一个最短的操作序列(将每次翻转N-1枚硬币成为一次操作). 输入输出格式 输入格式: 输入只有一行,包含一个自然数N(N为不大于100的偶数). 输出格式: 输出文件的第一行包含一个整数S,表示最少需要的操作次数.接下来的S行每行分别表示每次操作后桌上硬币的状态(一行包含N个整数(0或1),表示每个硬币的状态:0――正面向上,

题解 Luogu P2499: [SDOI2012]象棋

关于这道题, 我们可以发现移动顺序不会改变答案, 具体来说, 我们有以下引理成立: 对于一个移动过程中的任意一个移动, 若其到达的位置上有一个棋子, 则该方案要么不能将所有棋子移动到最终位置, 要么可以通过改变顺序使这一次移动合法 证明: 考虑到达位置上的那个棋子, 如果它没有到达最终位置, 则我们考虑将该棋子移至下一步, 如果下一步还有没有到达最终位置的棋子, 则也移动它 否则直接调换这两个棋子的移动顺序即可 好的我们去除了题目中的要求: 「移动过程中不能出现多颗棋子同时在某一格的情况」, 接

题解 Luogu P3370

讲讲这题的几种做法: 暴力匹配法 rt,暴力匹配,即把字符串存起来一位一位判相等 时间复杂度$ O(n^2·m) $ 再看看数据范围 $n\le10^5,m\le10^3$ 当场爆炸.当然有暴力分 代码(20pts): #include <bits/stdc++.h> using namespace std; char c[100001][1001]; bool pd(int x, int y) { int l1 = strlen(c[x]), l2 = strlen(c[y]); if(l1

题解 luogu P4415 【[COCI2006-2007#2] KOLONE】

一道纯模拟题. 将两队合并为一个字符串,用一个数组记录蚂蚁的方向,左队为0,右队为1.每一个时间点,两个两个字符地扫一遍字符串.由于0只能往右走,1只能往左走,所以只要在当前的两个字符中,第一个是0,第二个是1,就执行交换(同时交换方向数组和字符串),并且扫描指针加2.否则指针加1. 需要注意的是队伍合并时1左队顺序需要翻转. 自我感觉讲得比较明白了, 下贴代码: #include <bits/stdc++.h> using namespace std; int n1, n2, t, a[10

题解 luogu P1501【[国家集训队]Tree II】(Link-Cut-Tree)

Link-Cut-Tree 的懒标记下传正确食用方法. 我们来逐步分析每一个操作. 1:+ u v c:将u到v的路径上的点的权值都加上自然数c; 解决方法: 很显然,我们可以 split(u,v) 来提取u,v这一段区间,提取完了将 Splay(v),然后直接在v上打加法标记add即可. 代码: inline void pushadd(ll x,ll val){//打标记 s[x]+=sz[x]*val,v[x]+=val,add[x]+=val; s[x]%=MOD,v[x]%=MOD,ad