Light Up the Grid (The 2024 ICPC Asia Shenyang Regional Contest)

E. Light Up the Grid ( The 3rd Universal Cup. Stage 19: Shenyang)

在这里插入图片描述
在这里插入图片描述

题目描述

小 Q 设计了一款基于 2x2 网格的益智游戏,网格中的每个格子可以是开启状态(on)或关闭状态(off)。当一个格子被翻转时,其状态会在关闭和开启之间切换。游戏中可以执行以下四种操作,每种操作对应不同的代价:

  1. 翻转一个单独的格子,代价为a0
  2. 翻转一整行的格子,代价为 a1
  3. 翻转一整列的格子,代价为 a2
  4. 翻转所有格子,代价为 a3

游戏已经开始,但屏幕出现故障,导致小 Q 无法看到当前网格的状态。他唯一的反馈是,当执行某种操作后,所有格子都开启时会触发一个提示音。

小 Q 已知所有可能的初始网格状态,并希望找到一种操作序列,使得无论初始网格是什么状态,经过一系列操作后,都能保证听到提示音。请计算这种操作序列的最小总代价。


输入格式

第一行包含五个整数 T,a0,a1,a2,a3:

  • T:测试用例数 ( 1 ≤ T ≤ 1041 ≤ T ≤ 1 0 4 1 ≤ T ≤ 104 ) (1≤T≤1041 \leq T \leq 10^41≤T≤104) (1T1041T1041T104)
  • a0,a1,a2,a3:对应四种操作的代价 ( 1 ≤ a 0 , a 1 , a 2 , a 3 ≤ 1061 ≤ a 0 , a 1 , a 2 , a 3 ≤ 1 0 6 1 ≤ a 0 , a 1 , a 2 , a 3 ≤ 106 ) 。 (1≤a0,a1,a2,a3≤1061 \leq a_0, a_1, a_2, a_3 \leq 10^61≤a0,a1,a2,a3≤106)。 (1a0,a1,a2,a31061a0,a1,a2,a31061a0,a1,a2,a3106)

接下来是 TTT 组测试用例。对于每个测试用例:

  • 第一行包含一个整数 m,表示可能的初始网格数目 ( 1 ≤ m ≤ 161 ≤ m ≤ 161 ≤ m ≤ 16 ) (1≤m≤161 \leq m \leq 161≤m≤16) (1m161m161m16)
  • 接下来是 m 个网格,每个网格由两行构成,每行包含两个字符 ′0′或 ′1′,中间可能有空行分隔。

网格的每个格子定义如下:

  • ′0′:表示关闭状态。
  • ′1′:表示开启状态。

保证每个测试用例中的网格状态互不相同。


输出格式

对于每个测试用例,输出一个整数,表示使所有格子开启的最小总代价。


解题思路

1. 网格状态的表示
  • 2x2 的网格有

    2 4 = 16 2^4 = 16 24=16

    种可能的状态。可以用一个整数(0-15)表示网格状态,二进制的每一位表示对应格子的状态。

    • 例如,网格:

      1 0
      0 1
      

      对应状态为

      100 1 2 = 9 1001_2 =9 10012=9

2. 操作对状态的影响
  • 操作的具体效果可以用异或运算(^)表示:
    • 翻转单个格子:翻转对应位置的二进制位。
    • 翻转整行或整列:翻转整行或整列对应的二进制位。
    • 翻转所有格子:异或上 111 1 2 = 15 1111_2 = 15 11112=15
3. 使用 Floyd-Warshall 预处理最短路径
  • 使用 16 个状态构建图,每两个状态之间的边权为从一种状态到另一种状态的代价。
  • 通过 Floyd-Warshall 算法预处理状态之间的最短路径 d i s [ i ] [ j ] dis[i][j] dis[i][j]
4. 动态规划求解操作序列
  • 定义 d p [ m s ] [ j ] dp[ms][j] dp[ms][j]:表示当前已覆盖状态集合为 ms,最后一个执行的操作对应状态为 j的最小代价。
  • 初始状态:
    • 如果只操作一个状态 j, d p [ 1 < < j ] [ j ] = d i s [ j ] [ 15 ] dp[1 << j][j] = dis[j][15] dp[1<<j][j]=dis[j][15]
  • 转移方程:
    • 对于任意状态集合 msmsms 和状态 jjj,尝试扩展到状态 kkk: d p [ m s ∣ ( 1 < < k ) ] [ k ] = m i n ⁡ ( d p [ m s ∣ ( 1 < < k ) ] [ k ] , d p [ m s ] [ j ] + d i s [ ( j ⊕ 15 ) ⊕ k ] [ 15 ] ) ) dp[ms∣(1<<k)][k]=min⁡(dp[ms∣(1<<k)][k],dp[ms][j]+dis[(j⊕15)⊕k][15])) dp[ms(1<<k)][k]=min(dp[ms(1<<k)][k],dp[ms][j]+dis[(j15)k][15]))
  • 最终答案为: a n s [ m s ] = m i n ⁡ ( d p [ m s ] [ j ] ) , ∀ j ans[ms]=min⁡(dp[ms][j]),∀j ans[ms]=min(dp[ms][j]),j
5. 查询解答
  • 对于每个测试用例,将网格集合转换为对应的状态集合 ms,然后输出 ans[ms]。

代码实现

#include <map>
#include <set>
#include <fstream>
#include <queue>
#include <deque>
#include <stack>
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdio>
#include <bitset>
#include <iomanip>
#define endl '\n'
#define int long long
#define Max(a, b) (((a) > (b)) ? (a) : (b))
#define Min(a, b) (((a) < (b)) ? (a) : (b))
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;

int a0, a1, a2, a3;
int dis[16][16];
int dp[1 << 16][16];
int ans[1 << 16];
const int inf = 1e9 + 7;

void ckmin(int &x, int y)
{
    x = min(x, y);
}

void add(int x, int y, int w)
{
    ckmin(dis[x][y], w);
    ckmin(dis[y][x], w);
}

void init()
{
    cin >> a0 >> a1 >> a2 >> a3;

    memset(dis, inf, sizeof(dis));

    for (int i = 0; i < 16; i++)
    {
        // 0
        for (int j = 0; j < 4; j++)
        {
            add(i, (i ^ (1 << j)), a0);
        }
        // 1
        // 1100,0011
        add(i, (i ^ 12), a1);
        add(i, (i ^ 3), a1);

        // 2
        // 1010,0101
        add(i, (i ^ 10), a2);
        add(i, (i ^ 5), a2);

        // 3
        // 1111
        add(i, (i ^ 15), a3);
    }

    for (int i = 0; i < 16; i++)
    {
        dis[i][i] = 0;
    }

    dis[15][15] = 2 * min({a0, a1, a2, a3});

    for (int k = 0; k < 16; k++)
    {
        for (int i = 0; i < 16; i++)
        {
            for (int j = 0; j < 16; j++)
            {
                ckmin(dis[i][j], dis[i][k] + dis[k][j]);
            }
        }
    }

    memset(dp, inf, sizeof(dp));
    memset(ans, inf, sizeof(ans));
    for (int i = 0; i < 16; i++)
    {
        dp[1 << i][i] = dis[i][15];
    }

    for (int ms = 1; ms < 1 << 16; ms++)
    {
        for (int j = 0; j < 16; j++)
        {
            if ((ms >> j & 1) == 0)
                continue;
            for (int k = 0; k < 16; k++)
            {
                if (ms >> k & 1)
                    continue;
                ckmin(dp[ms | (1 << k)][k], dp[ms][j] + dis[(j ^ 15) ^ k][15]);
            }
        }
    }

    for (int ms = 1; ms < 1 << 16; ms++)
    {
        for (int j = 0; j < 16; j++)
        {
            ckmin(ans[ms], dp[ms][j]);
        }
    }
}

void solve()
{
    int n;
    cin >> n;
    int ms = 0;
    for (int i = 0; i < n; i++)
    {
        string p, q;
        cin >> p >> q;

        int x = 0;
        if (p[0] == '1')
        {
            x += 8;
        }
        if (p[1] == '1')
        {
            x += 4;
        }
        if (q[0] == '1')
        {
            x += 2;
        }
        if (q[1] == '1')
        {
            x += 1;
        }
        ms |= (1 << x);
    }
    cout << ans[ms] << endl;
}

signed main()
{
    BoBoowen;
    int T;
    cin >> T;

    init();

    while (T--)
    {
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值