- 题解
Day5
- @ 2025-8-27 21:30:15
T5
题目概述
题目要求我们计算通过一系列逻辑运算(AND 或 OR)后,最终结果为 true 的不同输入组合的数量。给定 N 个逻辑运算符("AND" 或 "OR"),我们需要确定每个变量的取值(true 或 false),使得按照给定的顺序进行运算后,最终结果为 true 的方案数。
方法一:动态规划解法
思路分析
动态规划是解决此问题的直观方法。我们可以定义状态 dp[i][j],其中:
i表示处理到第 i 个运算符j表示当前运算的中间结果(0 表示 false,1 表示 true)
初始状态为 dp[0][0] = dp[0][1] = 1,因为在开始任何运算之前,结果为 true 和 false 的方案数各为 1(可以理解为初始状态)。
状态转移
对于每个运算符,我们需要考虑当前输入变量 x 的两种可能取值(0 或 1),并更新状态:
-
AND 运算符 (&):
- 如果 x=0:
- 无论当前结果是什么,AND 运算后结果都会变为 0
dp[i][0] += dp[i-1][0] + dp[i-1][1]
- 如果 x=1:
- 结果保持原值
dp[i][0] += dp[i-1][0]dp[i][1] += dp[i-1][1]
- 如果 x=0:
-
OR 运算符 (|):
- 如果 x=0:
- 结果保持原值
dp[i][0] += dp[i-1][0]dp[i][1] += dp[i-1][1]
- 如果 x=1:
- 无论当前结果是什么,OR 运算后结果都会变为 1
dp[i][1] += dp[i-1][0] + dp[i-1][1]
- 如果 x=0:
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxm = 3e5+5;
int dp[maxm][2]; // dp[i][j]表示前i步,y为j的方案数
int op[maxm]; // 存储运算符类型
int n;
void solve() {
cin >> n;
for(int i=1; i<=n; i++) {
string s; cin >> s;
if(s[0] == 'A') op[i] = 1; // AND
else if(s[0] == 'O') op[i] = 2; // OR
}
dp[0][0] = dp[0][1] = 1;
for(int i=1; i<=n; i++) {
if(op[i] == 1) { // AND
// x[i] = 0
dp[i][0] += dp[i-1][0] + dp[i-1][1];
// x[i] = 1
dp[i][0] += dp[i-1][0];
dp[i][1] += dp[i-1][1];
} else if(op[i] == 2) { // OR
// x[i] = 0
dp[i][0] += dp[i-1][0];
dp[i][1] += dp[i-1][1];
// x[i] = 1
dp[i][1] += dp[i-1][0] + dp[i-1][1];
}
}
cout << dp[n][1] << endl;
}
signed main() {
ios::sync_with_stdio(0);
solve();
return 0;
}
方法二:数学观察法
思路分析
通过观察可以发现,当遇到 OR 运算符时,方案数会呈指数增长。具体来说:
- 初始时,结果为 true 的方案数为 1(即初始状态)
- 对于每个 OR 运算符,如果选择 x=1,则无论之前结果如何,最终结果都会为 true
- 因此,每遇到一个 OR 运算符,可以将其视为增加
2^i种方案(其中 i 是该 OR 运算符的位置)
代码实现
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll ans = 1;
string op[67];
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> op[i];
for(int i = n; i >= 1; i--)
if(op[i][0] == 'O') ans += (1ll << i);
cout << ans;
return 0;
}
复杂度分析
-
动态规划方法:
- 时间复杂度:O(N)
- 空间复杂度:O(N)
-
数学方法:
- 时间复杂度:O(N)
- 空间复杂度:O(1)(如果不考虑输入存储)
总结
本题可以通过动态规划直接解决,也可以通过数学观察找到规律。动态规划方法更加通用,而数学方法则利用了本题的特殊性质,代码更为简洁。
T6
问题描述
高桥君在二维平面的原点(0,0)处。他有N次传送机会,每次可以选择以下三种传送方式之一:
- 从(x,y)传送到(x+A,y+B)
- 从(x,y)传送到(x+C,y+D)
- 从(x,y)传送到(x+E,y+F)
平面上有M个障碍点,不能传送到这些障碍点上。问高桥君完成N次传送后,有多少种不同的路径方案?答案需要对998244353取模。
解题思路
直接DP的问题
最直观的想法是使用动态规划,记录每次传送后的位置。设dp[i][x][y]表示第i次传送后位于(x,y)的方案数。但是这种方法的问题在于,坐标范围可能非常大,无法用数组存储。
关键观察
注意到每种传送方式的使用次数决定了最终位置。设三种传送方式分别使用了t₁、t₂、t₃次(t₁ + t₂ + t₃ = N),那么最终位置为: (x, y) = (t₁A + t₂C + t₃E, t₁B + t₂D + t₃F)
因此,我们可以将状态表示为(t₁, t₂, t₃),而不需要直接记录坐标。
解法一:记忆化搜索
使用记忆化搜索可以优雅地解决这个问题。定义dfs(x, y, z)表示三种传送方式分别使用了x、y、z次的方案数。
#include<bits/stdc++.h>
#define int long long
using namespace std;
set<pair<int,int>> s;
int n,m,a,b,c,d,e,f,g[301][301][301];
const int mod = 998244353;
int dfs(int x,int y,int z) {
int x0 = a*x + c*y + e*z;
int y0 = b*x + d*y + f*z;
if(s.find({x0,y0}) != s.end()) return 0; // 障碍点
if(x + y + z == n) return 1; // 完成N次传送
if(g[x][y][z]) return g[x][y][z]; // 记忆化
return g[x][y][z] = (dfs(x+1,y,z) + dfs(x,y+1,z) + dfs(x,y,z+1)) % mod;
}
signed main() {
ios::sync_with_stdio(false);
cin >> n >> m >> a >> b >> c >> d >> e >> f;
for(int i=0; i<m; i++) {
int x,y; cin >> x >> y;
s.insert({x,y});
}
cout << dfs(0,0,0) << endl;
return 0;
}
复杂度分析
- 时间复杂度:O(N³),因为状态数为组合数C(N+2,2) ≈ N²/2,每个状态处理时间为O(1)
- 空间复杂度:O(N³),用于存储记忆化数组
解法二:动态规划
也可以使用递推式的动态规划来解决。定义dp[i][u][v]表示前i次传送中,第一种方式用了u次,第二种方式用了v次的方案数。
#include <bits/stdc++.h>
using namespace std;
const int N = 310, mod = 998244353;
int n, m, a, b, c, d, e, f;
long long ans, dp[N][N][N];
map<pair<long long, long long>, bool> mp;
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m >> a >> b >> c >> d >> e >> f;
while (m--) {
int x, y;
cin >> x >> y;
mp[{x, y}] = 1;
}
dp[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int u = 0; u <= i; u++) {
for (int v = 0; u + v <= i; v++) {
int w = i - u - v;
long long x = 1ll * u * a + 1ll * v * c + 1ll * w * e;
long long y = 1ll * u * b + 1ll * v * d + 1ll * w * f;
if (!mp.count({x, y})) {
dp[i][u][v] = dp[i - 1][u][v];
if (u) dp[i][u][v] = (dp[i][u][v] + dp[i - 1][u - 1][v]) % mod;
if (v) dp[i][u][v] = (dp[i][u][v] + dp[i - 1][u][v - 1]) % mod;
if (i == n) ans = (ans + dp[i][u][v]) % mod;
}
}
}
}
cout << ans;
return 0;
}
复杂度分析
- 时间复杂度:O(N³),三重循环
- 空间复杂度:O(N³),可以优化到O(N²)但代码会更复杂