2024-04-13 22:47阅读: 966评论: 3推荐: 4

AtCoder Beginner Contest 349

A - Zero Sum Game (abc349 A)

题目大意

n个人游戏,每局有一人 +1分,有一人 1分。

给定最后前 n1个人的分数,问第 n个人的分数。

解题思路

零和游戏,所有人总分是 0,因此最后一个人的分数就是前 n1个人的分数和的相反数。

神奇的代码
n = input()
print(-sum([int(i) for i in input().split()]))


B - Commencement (abc349 B)

题目大意

对于一个字符串,如果对于所有 i1,都有恰好 02 个自负出现i次,则该串是好串。

给定一个字符串s,问它是不是好串。

解题思路

|s|只有 100,统计每个字符的出现次数,再枚举 i即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s;
cin >> s;
bool ok = true;
map<char, int> cnt;
for (auto c : s)
cnt[c]++;
auto check = [&](int c) {
int cc = 0;
for (auto& [k, v] : cnt) {
cc += (v == c);
}
return cc == 0 || cc == 2;
};
for (int i = 1; i <= s.size(); ++i) {
ok &= check(i);
}
cout << (ok ? "Yes" : "No") << endl;
return 0;
}


C - Airport Code (abc349 C)

题目大意

给定一个字符串s和字符串 t,问字符串 t能否从字符串 s得到。操作为:

  • s挑三个字母,不改变顺序变成 t
  • s挑两个字母,加上X,不改变顺序变成 t

解题思路

就子序列匹配问题。就近匹配原则即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s, t;
cin >> s >> t;
if (t.back() == 'X')
t.pop_back();
auto pos = s.find_first_of(tolower(t[0]));
for (int i = 1; i < t.size() && pos < s.size(); ++i) {
pos = s.find_first_of(tolower(t[i]), pos + 1);
}
if (pos < s.size())
cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}


D - Divide Interval (abc349 D)

题目大意

给定[l,l+1,...,r1,r)序列,拆分成最少的序列个数,使得每个序列形如 [2ij,2i(j+1))

给出拆分方案。

解题思路

拆分的序列个数最小,那肯定想让一个序列尽可能的长,而长的话,就是让2i尽可能大。

因此就贪心地让2i尽可能大,即 l=2ij,这里的i是最大的 i(这意味着 j是奇数),并且 r2i(j+1),如果 r>2i(j+1),那说明 2i太大了,就变成 2(i1)2j来试试。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL l, r;
cin >> l >> r;
vector<array<LL, 2>> ans;
while (l < r) {
LL p2 = 1;
LL bl = l;
while (bl % 2 == 0 && l + p2 <= r) {
bl >>= 1;
p2 <<= 1;
}
while (l + p2 > r) {
p2 >>= 1;
bl <<= 1;
}
ans.push_back({l, l + p2});
l += p2;
}
cout << ans.size() << '\n';
for (auto& i : ans) {
cout << i[0] << ' ' << i[1] << '\n';
}
return 0;
}


E - Weighted Tic-Tac-Toe (abc349 E)

题目大意

给定3×3的网格,高桥和青木画 OX

每个格子有分数。

若存在同行同列或同对角线,则对应方赢,否则全部画满后,所画格子的分数和较大者赢。

问最优策略下,谁赢。

解题思路

朴素的博弈dp,状态即为当前的棋盘样子,总状态数为 39=2e4,直接搜索即可。

即设 dp[i]表示当前局面 i的当前操作者(称为先手)的必赢(dp[i]=1)或必输 (dp[i]=0)

转移,则枚举当前操作者的行为,即选择哪个格子,进入后继状态。

如果所有后继状态都是(先手)必赢,那么当前状态则是(先手)必输,即dp[i]=0如果所有dp[j]=1j是后继状态。

否则,如果有一个后继状态是先手必输,那么当前状态的先手就可以控制局面走向该状态,使得当前状态是必胜态,即dp[i]=1如果存在一个dp[j]=0

转移显而易见是O(9),总状态数只有 O(2e4),因此朴素搜索就可以通过了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
typedef array<array<LL, 3>, 3> tu;
tu a;
for (auto& x : a)
for (auto& y : x)
cin >> y;
map<tu, int> dp;
auto check_end = [&](tu& pos) -> int {
LL p1 = 0, p2 = 0;
int left = 0;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
left += !pos[i][j];
p1 += (pos[i][j] == 1) * a[i][j];
p2 += (pos[i][j] == 2) * a[i][j];
}
}
if (left == 0) {
if (p1 > p2)
return 0;
else
return 1;
}
for (int i = 0; i < 3; ++i) {
if (pos[i][0] == pos[i][1] && pos[i][1] == pos[i][2] &&
pos[i][0] != 0) {
if (pos[i][0] == 1)
return 0;
else
return 1;
}
if (pos[0][i] == pos[1][i] && pos[1][i] == pos[2][i] &&
pos[0][i] != 0) {
if (pos[0][i] == 1)
return 0;
else
return 1;
}
}
if (pos[0][0] == pos[1][1] && pos[1][1] == pos[2][2] &&
pos[0][0] != 0) {
if (pos[0][0] == 1)
return 0;
else
return 1;
}
if (pos[0][2] == pos[1][1] && pos[1][1] == pos[2][0] &&
pos[0][2] != 0) {
if (pos[0][2] == 1)
return 0;
else
return 1;
}
return -1;
};
auto dfs = [&](auto self, tu& pos, int role) -> bool {
int status = check_end(pos);
if (status != -1) {
return dp[pos] = status == role;
}
if (dp.find(pos) != dp.end())
return dp[pos];
bool lose = false;
for (auto& i : pos)
for (auto& j : i) {
if (j)
continue;
j = (role + 1);
lose |= (!self(self, pos, role ^ 1));
j = 0;
}
return dp[pos] = lose ? 1 : 0;
};
tu ini{};
bool win = dfs(dfs, ini, 0);
cout << (win ? "Takahashi" : "Aoki") << endl;
return 0;
}


F - Subsequence LCM (abc349 F)

题目大意

给定一个序列am,问子序列的数量,使得其lcm(最小公倍数)为 m

子序列之间的不同,当且仅当有元素在原位置不一样,即使它们的数字可能是一样的。

解题思路

我们可以依次考虑每个a,选或不选,很显然是O(2n)

我们不能维护每个数选择的状态,但是要维护怎样的状态呢?是怎样的中间状态能够导出它们的最小公倍数呢。

一个简单的办法就是记录此时的最小公倍数,即dp[i][j]表示考虑前 i个数,选择若干后,最小公倍数是 j的方案数。转移就考虑当前数ai选或不选, 如果选,则通过jai可以得到新的最小公倍数,转移到后继状态。

但是状态数有 O(105×1016),太大了。

要简化状态数,一个比较明显的观察的是,并不是所有的j都是必要的,只有 m的因子的那些 j才有可能转移到 m,其他的都不可能转移到 m,因此第二维状态数可以减小,但因子数的数量级大概是根号级别,也就是说还有 108左右,还是不行。

如何继续简化呢,那得知道如何求解最小公倍数。两个数a,b的最小公倍数是abgcd(ab),但还有另一种求法,这种求法可以一次求解 n个数的最小公倍数。

根据其定义,假设最小公倍数是m,那就意味着 m是每个数的倍数。

从质因数的角度来思考倍数关系,那就是 m的每个质因子的幂 每个数对应质因子的幂。

为了让m是最小的公倍数,那就让m的每个质因子的幂就是这些数对应质因子幂的最大值,这样在保证是倍数的关系下,还能最小。(最大公因数对应的其实就是幂的最小值)

从上述求最小公倍数的角度来理解状态j,其实就是记录了m的各个质因子的幂的值。但稍微细想,结合转移的取幂的最大值,会发现,这里的状态还是冗余的:我们无需记录各个质因子的幂,而是记录是否达到m对应的幂就可以了:还是能够转移,能够从最后得到正确答案。

由此就可以得到更加简化的状态:设 dp[i][j]表示考虑前 i个数,选择若干个后,其最小公倍数的各个质数幂的最大值是否达到 m对应的质数幂的值的状态为 j(对于每个 m的质数,都有 未达到达到这一01 状态,因此j是一个二进制压缩的状态)的选择方案数。初始条件是 dp[0][0]=0

转移就考虑,选择当前数后,状态 j是否会变化。因此要事先预处理每个数选择后对这一状态的影响。即事先对m质因数分解,再预处理每个 ai对转移的影响。

考虑时间复杂度, m1016,至多有13个质数,总的复杂度是 O(213105),粗略算也有 1e9了。当前的 dp还过不了。考虑进一步优化。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
LL m;
cin >> n >> m;
bool one = (m == 1);
vector<pair<LL, int>> fac;
int up = 1e8;
for (int i = 2; i <= up; ++i) {
if (m % i == 0) {
fac.push_back({i, 0});
while (m % i == 0) {
m /= i;
fac.back().second++;
}
}
}
if (m != 1) {
fac.push_back({m, 1});
}
vector<int> a;
for (int i = 0; i < n; ++i) {
LL x;
cin >> x;
bool ok = true;
int sign = 0;
for (int j = 0; j < fac.size(); ++j) {
auto& [p, v] = fac[j];
int cnt = 0;
while (x % p == 0) {
x /= p;
++cnt;
}
ok &= (cnt <= v);
if (cnt == v)
sign |= (1 << j);
}
ok &= (x == 1);
if (ok)
a.push_back(sign);
}
vector<int> dp(1 << fac.size(), 0);
dp[0] = 1;
for (int x : a) {
vector<int> dp2 = dp;
for (int i = 0; i < (1 << fac.size()); ++i) {
dp2[i | x] = (dp2[i | x] + dp[i]);
if (dp2[i | x] >= mo)
dp2[i | x] -= mo;
}
dp.swap(dp2);
}
int ans = dp.back();
if (one) {
ans = (ans - 1 + mo) % mo;
}
cout << ans << '\n';
return 0;
}


欲知后事如何,且等作业写完后再写

如何继续优化呢,注意到2e5个数,我们预处理它们对状态的转移的影响后,也即变成一堆010101标记后,容易意识到的一点是它们有很多重复的,比如很多数选择后它们对状态的影响是0,因此可以把它们整合到一起考虑。

即处理出cnt[i]表示标记 i出现的次数,标记种类的数量级同样是 O(213),然后对每个标记 考虑如何选(有2cnt[i]1)种选法,然后标记影响,转移到后继状态即可。这样时间复杂度是 O(226,可以通过了。

最后要特判下m=1的情况。

超时的1e9
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
long long qpower(long long a, long long b) {
long long qwq = 1;
while (b) {
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
LL m;
cin >> n >> m;
bool one = (m == 1);
vector<pair<LL, int>> fac;
int up = 1e8;
for (int i = 2; i <= up; ++i) {
if (m % i == 0) {
fac.push_back({i, 0});
while (m % i == 0) {
m /= i;
fac.back().second++;
}
}
}
if (m != 1) {
fac.push_back({m, 1});
}
vector<int> a;
for (int i = 0; i < n; ++i) {
LL x;
cin >> x;
bool ok = true;
int sign = 0;
for (int j = 0; j < fac.size(); ++j) {
auto& [p, v] = fac[j];
int cnt = 0;
while (x % p == 0) {
x /= p;
++cnt;
}
ok &= (cnt <= v);
if (cnt == v)
sign |= (1 << j);
}
ok &= (x == 1);
if (ok)
a.push_back(sign);
}
vector<int> cnt(1 << fac.size(), 0);
vector<int> dp(1 << fac.size(), 0);
for (auto& i : a)
cnt[i]++;
dp[0] = 1;
for (int x = 0; x < cnt.size(); ++x) {
vector<int> dp2 = dp;
int w = qpower(2, cnt[x]) - 1;
if (w < 0)
w += mo;
for (int i = 0; i < (1 << fac.size()); ++i) {
dp2[i | x] = dp2[i | x] + 1ll * dp[i] * w % mo;
if (dp2[i | x] >= mo)
dp2[i | x] -= mo;
}
dp.swap(dp2);
}
int ans = dp.back();
if (one) {
ans = (ans - 1 + mo) % mo;
}
cout << ans << '\n';
return 0;
}

官方题解貌似可以通过zeta变换+莫比乌斯容斥降到O(13213)

G - Palindrome Construction (abc349 G)

题目大意

<++>

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/18133534

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(966)  评论(3编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.