题解:P3921 小学数学题
题目
题目链接
P3921 小学数学题
有
另外,在任意时刻,有一些条件需要满足,其中第一种条件有
第一种:
第二种 : 当
求: 至少需要传送器几次到对岸;在保证次数最少的前提下,求方案数。
对于
对于另外
对于
解析
0~10 分做法
直接输出 -1 0。
30~70 分做法
由于对于
设
此时只需枚举每次通过传送门的人,bfs即可。
常数卡的好可以拿到
代码此处不展示了。
100 分做法
似乎其他题解有更方便的做法,需要的可以看其他人的题解。
此处提供一种做法:
首先,对于 30~70 分的做法,因为其状态转移的次数过多所以使得其超时。
对于状态转移过多的情况,可以重新设计状态进行转移。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 15;
const long long INF = 1e18;
struct State {
int x, y, z;
State(int x_, int y_, int z_) : x(x_), y(y_), z(z_) {}
};
int n, m1, m2, r;
long long dp[1 << MAXN][MAXN + 1][MAXN + 1]; // dp[mask][y][z]
long long cnt[1 << MAXN][MAXN + 1][MAXN + 1]; // 方案数
bool can[1 << MAXN]; // 状态mask是否合法
bool vis[1 << MAXN][MAXN + 1][MAXN + 1]; // 是否访问过该状态
vector<State> Bucket[1 << MAXN][MAXN + 1]; // 桶排序降低时间复杂度
// 约束条件
int a[55], b[55]; // 第一种条件:a和b必须同侧
int c[55], d[55], e[55]; // 第二种条件:a在一侧时,b和c不能同时在另一侧
int main() {
memset(can, true, sizeof(can));
cin >> n >> m1 >> m2 >> r;
// 读取第一种约束
for (int i = 0; i < m1; ++i) {
cin >> a[i] >> b[i];
a[i]--; b[i]--; // 转为0基
}
// 读取第二种约束
for (int i = 0; i < m2; ++i) {
cin >> c[i] >> d[i] >> e[i];
c[i]--; d[i]--; e[i]--; // 转为0基
}
// 预处理can数组:检查每个mask是否满足所有约束
for (int mask = 0; mask < (1 << n); ++mask) {
// 检查第一种约束
for (int i = 0; i < m1; ++i) {
bool a_in = (mask >> a[i]) & 1;
bool b_in = (mask >> b[i]) & 1;
if (a_in != b_in) {
can[mask] = false;
break;
}
}
if (!can[mask]) continue;
// 检查第二种约束
for (int i = 0; i < m2; ++i) {
bool c_in = (mask >> c[i]) & 1;
bool d_in = (mask >> d[i]) & 1;
bool e_in = (mask >> e[i]) & 1;
// 若c在一侧,d和e都在另一侧,则不合法
if (d_in != c_in && e_in != c_in) {
can[mask] = false;
break;
}
}
}
// 初始化DP
for (int mask = 0; mask < (1 << n); ++mask)
for (int y = 0; y <= n; ++y)
for (int z = 0; z <= n; ++z)
dp[mask][y][z] = INF;
dp[0][0][0] = 0;
// 0-1 BFS计算最少次数
deque<State> q;
q.emplace_back(0, 0, 0);
while (!q.empty()) {
State s = q.front();
q.pop_front();
int mask = s.x;
int y = s.y;
int z = s.z;
if (vis[mask][y][z]) continue;
if (dp[mask][y][z] > dp[(1 << n) - 1][0][0]) continue;
vis[mask][y][z] = true;
// 当传送器在原岸(y=0),当前状态必须合法
if (y == 0 && !can[mask]) continue;
// 若所有妖精都处理完毕(z == n),可以传送一次
if (z == n) {
int new_mask = mask;
int new_y = 0;
int new_z = 0;
if (dp[new_mask][new_y][new_z] > dp[mask][y][z] + 1) {
dp[new_mask][new_y][new_z] = dp[mask][y][z] + 1;
q.emplace_back(new_mask, new_y, new_z);
}
} else {
// 不选第z只妖精
int new_mask = mask;
int new_y = y;
int new_z = z + 1;
if (dp[new_mask][new_y][new_z] > dp[mask][y][z]) {
dp[new_mask][new_y][new_z] = dp[mask][y][z];
q.emplace_front(new_mask, new_y, new_z); // 0 cost,放前面
}
// 选第z只妖精(如果传送器还能装)
if (y < r) {
new_mask = mask ^ (1 << z);
new_y = y + 1;
new_z = z + 1;
if (dp[new_mask][new_y][new_z] > dp[mask][y][z]) {
dp[new_mask][new_y][new_z] = dp[mask][y][z];
q.emplace_front(new_mask, new_y, new_z); // 0 cost,放前面
}
}
}
}
// 目标状态:所有妖精到对岸(mask=(1<<n)-1),传送器在原岸(y=0),处理完毕(z=0)
int target_mask = (1 << n) - 1;
long long min_steps = dp[target_mask][0][0];
if (min_steps >= INF) {
cout << "-1 0" << endl;
return 0;
}
// 统计方案数:反向拓扑排序
memset(cnt, 0, sizeof(cnt));
cnt[target_mask][0][0] = 1;
// 收集所有有效状态,并按dp值从大到小排序,同一dp值按z从大到小排序
vector<State> states;
for (int mask = 0; mask < (1 << n); ++mask)
for (int y = 0; y <= n; ++y)
for (int z = 0; z <= n; ++z)
if (vis[mask][y][z] && dp[mask][y][z] <= min_steps)
Bucket[dp[mask][y][z]][z].emplace_back(mask, y, z);
// 排序:先按dp值降序,再按z降序
for (int i = min_steps; i >= 0; --i)
for (int j = n; j >= 0; --j)
for (int k = Bucket[i][j].size() - 1; k >= 0; --k)
states.push_back(Bucket[i][j][k]);
// 处理每个状态,计算方案数
for (const State& s : states) {
int mask = s.x;
int y = s.y;
int z = s.z;
long long current_cnt = cnt[mask][y][z];
if (current_cnt == 0) continue;
// 情况1:当前状态是由"不选第z-1只妖精"转移而来
if (z > 0) {
int prev_z = z - 1;
int prev_y = y;
int prev_mask = mask;
if (vis[prev_mask][prev_y][prev_z] && dp[prev_mask][prev_y][prev_z] == dp[mask][y][z]) {
cnt[prev_mask][prev_y][prev_z] += current_cnt;
}
}
// 情况2:当前状态是由"选第z-1只妖精"转移而来
if (z > 0 && y > 0) {
int fairy = z - 1;
int prev_mask = mask ^ (1 << fairy);
int prev_y = y - 1;
int prev_z = z - 1;
if (prev_y < r && vis[prev_mask][prev_y][prev_z] && dp[prev_mask][prev_y][prev_z] == dp[mask][y][z]) {
cnt[prev_mask][prev_y][prev_z] += current_cnt;
}
}
// 情况3:当前状态是由"传送"步骤转移而来(即当前状态是传送后的状态)
if (y == 0 && z == 0) {
int prev_z = n;
int prev_mask = mask;
for (int prev_y = 1; prev_y <= n; ++prev_y) { // 传送时至少有1只妖精
if (prev_y > r) continue; // 传送时数量不超过r
if (vis[prev_mask][prev_y][prev_z] && dp[prev_mask][prev_y][prev_z] == dp[mask][y][z] - 1) {
cnt[prev_mask][prev_y][prev_z] += current_cnt;
}
}
}
}
// 初始状态的方案数
long long total = cnt[0][0][0];
cout << min_steps << " " << total << endl;
return 0;
}