E. Light Up the Grid ( The 3rd Universal Cup. Stage 19: Shenyang)
题目描述
小 Q 设计了一款基于 2x2 网格的益智游戏,网格中的每个格子可以是开启状态(on)或关闭状态(off)。当一个格子被翻转时,其状态会在关闭和开启之间切换。游戏中可以执行以下四种操作,每种操作对应不同的代价:
- 翻转一个单独的格子,代价为a0。
- 翻转一整行的格子,代价为 a1。
- 翻转一整列的格子,代价为 a2。
- 翻转所有格子,代价为 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) (1≤T≤1041≤T≤1041≤T≤104)。
- 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)。 (1≤a0,a1,a2,a3≤1061≤a0,a1,a2,a3≤1061≤a0,a1,a2,a3≤106)。
接下来是 TTT 组测试用例。对于每个测试用例:
- 第一行包含一个整数 m,表示可能的初始网格数目 ( 1 ≤ m ≤ 161 ≤ m ≤ 161 ≤ m ≤ 16 ) (1≤m≤161 \leq m \leq 161≤m≤16) (1≤m≤161≤m≤161≤m≤16)。
- 接下来是 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[(j⊕15)⊕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;
}