2023-06-24 22:16阅读: 780评论: 4推荐: 3

AtCoder Beginner Contest 307

A - Weekly Records (abc307 A)

题目大意

给定n周每天的散步量,求每周七天的散步量的和。

解题思路

累计求和即可。

神奇的代码
#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);
int n;
cin >> n;
while(n--){
int sum = 0;
for(int i = 0; i < 7; ++ i){
int a;
cin >> a;
sum += a;
}
cout << sum << ' ';
}
return 0;
}


B - racecar (abc307 B)

题目大意

给定n个字符串 s,问能否选择两个 i,j,满足 ij,且 si+sj是个回文串。

解题思路

只有100个串,直接 O(n2)枚举判断即可。

判断回文可以将串反转后与原先的串比较是否相同。

神奇的代码
#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);
int n;
cin >> n;
vector<string> s(n);
for(auto &i : s)
cin >> i;
auto palind = [&](int x, int y){
string l = s[x] + s[y];
string r = l;
reverse(r.begin(), r.end());
return l == r;
};
auto ok = [&](){
for(int i = 0; i < n; ++ i)
for(int j = 0; j < n; ++ j){
if (i != j && palind(i, j))
return true;
}
return false;
};
if (ok())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


C - Ideal Sheet (abc307 C)

题目大意

给定两个二维网格的模式图,有一些格子是的,有一些格子是透明的。

问能否通过这两个模式图得到一个期望的模式图,要求格子的属性(透明)相同。

操作是将这两个模式图分别盖印到一个无穷大的二维网格上,仅能盖一次,然后通过裁剪得到期望模式图。

注意盖印的黑色格子都必须保留,不得裁剪掉。

解题思路

因为模式图大小只有10×10,因此直接 O(104)枚举两个模式图的盖印位置,然后判断盖印后的结果是否与期望模式图相同。

注意盖印后的黑色格子都必须在期望模式图范围内,所以还需统计期望模式图范围内的两个模式图的黑色格子数,不能有超出期望模式图范围的黑色格子。

神奇的代码
#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);
array<int, 3> h, w;
array<vector<string>, 3> d;
array<int, 3> blk{};
for(int i = 0; i < 3; ++ i){
cin >> h[i] >> w[i];
d[i].resize(h[i]);
for(int j = 0; j < h[i]; ++ j){
cin >> d[i][j];
blk[i] += count(d[i][j].begin(), d[i][j].end(), '#');
}
}
auto check = [&](int x1, int y1, int x2, int y2){
int tot = 0;
for(int i = 0; i < h[2]; ++ i)
for(int j = 0; j < w[2]; ++ j){
int target = (d[2][i][j] == '#');
int blk1 = (i >= x1 && j >= y1 && i - x1 < h[0] && j - y1 < w[0] && d[0][i - x1][j - y1] == '#');
int blk2 = (i >= x2 && j >= y2 && i - x2 < h[1] && j - y2 < w[1] && d[1][i - x2][j - y2] == '#');
tot += blk1 + blk2;
if (target != (blk1 || blk2))
return false;
}
return (tot == blk[0] + blk[1]);
};
auto ok = [&](){
for(int i = -10; i <= 10; ++ i)
for(int j = -10; j <= 10; ++ j)
for(int k = -10; k <= 10; ++ k)
for(int l = -10; l <= 10; ++ l){
if (check(i, j, k, l))
return true;
}
return false;
};
if (ok())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


D - Mismatched Parentheses (abc307 D)

题目大意

给定一个包含小写字母、()的字符串。每次选择一个开头是(,结尾是),且中间不包含这两个字符的子串,移除该子串。

问最终剩余的字符串是怎样的。

解题思路

每次移除的其实是一个最里面的合法的(),假设有(()()),每次移除的都是最里端的一对(),因此反复这么操作,合法的括号序列都会被移除掉。

因此我们对原字符串的每个(统计与之对应的),这样每个()之间的内容都会被移除。

根据这个对应关系就会跳过一些字符串(被移除了),剩下的就是没被移除的了。

神奇的代码
#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);
int n;
string s;
cin >> n >> s;
vector<int> r(n, -1);
stack<int> pos;
for(int i = 0; i < n; ++ i){
if (s[i] == '(')
pos.push(i);
else if (s[i] == ')' && !pos.empty()){
r[pos.top()] = i;
pos.pop();
}
}
for(int i = 0; i < n; ++ i){
if (r[i] != -1){
i = r[i];
}else {
cout << s[i];
}
}
cout << '\n';
return 0;
}


E - Distinct Adjacent (abc307 E)

题目大意

给定一个n个数的环形数组,每个数的取值为[1,m]

问有多少种取值情况,满足任意相邻两个数不相同。

解题思路

如果不是环形,答案很明显是m×(m1)(n1)。但由于环形,最后一位的取值难以确定是n1还是 n2,这取决于倒数第二位是否和第一位相同。

如何解决呢?其实问题的核心上面已经指明了:倒数第二位是否和第一位相同

  • 如果相同,则最后一位可取n1种情况。
  • 如果不相同,则最后一位可取 n2种情况。

由于每个数都是对称的,我们假设第一位填的数是 1。然后设 dp[i][0/1]表示前 i位,相邻两数不同(不考虑首尾),且最后一位与首位数字不同/相同的方案数。转移就看当前位是否与首位相同。

因为上述我们假定了首位取1,事实上首位取什么值情况都一样,而它有 m种取法,因此最终答案就是m×dp[n][0]

神奇的代码
#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, m;
cin >> n >> m;
array<int, 2> dp{0, 1};
for(int i = 1; i < n; ++ i){
array<int, 2> dp2{};
dp2[0] = 1ll * dp[0] * (m - 2) % mo + 1ll * dp[1] * (m - 1) % mo;
if (dp2[0] >= mo)
dp2[0] -= mo;
dp2[1] = dp[0];
dp.swap(dp2);
}
cout << 1ll * dp[0] * m % mo << '\n';
return 0;
}


F - Virus 2 (abc307 F)

题目大意

给定一张无向图,边有边权。

初始有a个点感染病毒。

持续 d天。对于第 i天,所有与已感染病毒的点距离不超过xi的点都会被感染病毒。

问每个点第一次被感染病毒的天数。

解题思路

因为有多个起点,我们建立一个超级源点s,它与所有已感染病毒的点连一条边权为 0的无向边。

考虑第一天,我们从超级源点 s开始 dijkstra,所有距离不超过 x的点都会被感染病毒。然后新感染的点又会与超级源点s连一条边权为 0的边。很显然当跑到距离大于x时我们就无需继续跑 dijkstra了。

之后第二天重复跑dijkstra,依次类推,时间复杂度是 O(dn\logm),是无法通过的。

究其原因,主要是舍弃了之前信息,重复操作了:当一个新的点u被感染时,它会影响与其相邻点v的最短路距离,这个相邻点只包含未被感染的点——因为已被感染的点v意味着与超级源点s有条 0边权的边,从v出发的最短路一定优于从uv的。因此对于已被感染的点未被感染的点的边的情况是可以继承上一次dijkstra,即复用前一天的dijkstra的优先队列,而不用重新求,因为没有变化。

换句话说,假设第一天有k个新感染的点 ui,我们只需要更新这些新感染的点未被感染的相邻点的最短距离,将其加入到 dijkstra的优先队列里。第二天就继续从这个优先队列开始dijkstra

这样,每条边最多只会被考虑两次——一次是正常的 dijkstra,另一次是当其中一个端点被感染病毒后重新考虑。

因此总的时间复杂度是 O(nlogm)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e18;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<array<int, 2>>> edge(n);
for(int i = 0; i < m; ++ i){
int u, v, w;
cin >> u >> v >> w;
-- u, -- v;
edge[u].push_back({v, w});
edge[v].push_back({u, w});
}
vector<LL> dis(n, inf);
vector<int> ans(n, -1);
priority_queue<pair<LL, int>> team;
int k;
cin >> k;
for(int i = 0; i < k; ++ i){
int u;
cin >> u;
-- u;
dis[u] = 0;
ans[u] = 0;
for(auto &[v, w] : edge[u]){
if (dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
team.push({-dis[v], v});
}
}
}
int d;
cin >> d;
for(int i = 0; i < d; ++ i){
int x;
cin >> x;
vector<int> infected;
while(!team.empty()){
auto [d, u] = team.top();
if (dis[u] > x)
break;
team.pop();
if (dis[u] != -d || ans[u] != -1)
continue;
ans[u] = i + 1;
infected.push_back(u);
for(auto &[v, w] : edge[u]){
if (dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
team.push({-dis[v], v});
}
}
}
for(auto &u : infected)
for(auto &[v, w]: edge[u]){
if (dis[v] > w){
dis[v] = w;
team.push({-dis[v], v});
}
}
}
for(int i = 0; i < n; ++ i)
cout << ans[i] << "\n";
return 0;
}


G - Approximate Equalization (abc307 G)

题目大意

给定一个有n个数的数组,可进行两种操作:

  • 选定一个i,令 ai=ai1,ai+1=ai+1+1
  • 选定一个i,令 ai=ai+1,ai+1=ai+11

问最少进行的操作次数,使得数组的极差不超过1

解题思路

观察操作,可以发现每次操作后,这个数组的总和sum是不变的。(其实可以看成有sum个小球, n1个隔板,每次操作其实就是移动一个隔板到相邻位置)

由于极差不超过1,那么最终每个数要么是 div=sumn,要么是 div+1,且至多有sum%n个数是后者。问题就是让哪些数是后者,哪些数是前者。

首先明确一点,如果给定最终的数组,求将该数组变成最终数组的操作次数,其最优方案是可以在O(n)内求出来。就是依次对每个数ai,将其变为目标值ti,需要从后一个数 ai+1多少次(操作二),还是多少次(操作一),然后这个的影响是持续的。

现在问题变成了让哪些数是div,哪些是div+1,这是个分配问题,设 dp[i][j]表示前 i个数,还有 jdiv+1的未分配的最小操作数。需要第二维状态一方面是要确保有moddiv+1,另一方面是需要知道由于前面的数的操作,当前数变成了多少。

对于当前数ai,知道了 j,我们就知道前面有多少个数是 div+1,多少个数是div,进而知道,在上述求解操作的影响下, ai变成了多少,然后就可以进一步求解 ai变成目标数所需要的操作次数了。

注意如果mod=sum%n是负数,则可以让div=div1,mod=nmod,这样就变回正数了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e18;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<LL> a(n);
for(auto &i : a)
cin >> i;
LL sum = accumulate(a.begin(), a.end(), 0ll);
LL div = sum / n, mod = sum % n;
if (mod < 0){
mod = n + mod;
div --;
}
vector<LL> dp(mod + 1, inf);
dp[mod] = 0;
LL presum = 0;
LL tot = 0;
for(auto &x : a){
vector<LL> dp2(mod + 1, inf);
for(int i = 0; i <= mod; ++ i){
LL cur = x - (tot + mod - i - presum);
dp2[i] = min(dp2[i], dp[i] + abs(cur - div));
if (i)
dp2[i - 1] = min(dp2[i - 1], dp[i] + abs(cur - div - 1));
}
presum += x;
tot += div;
dp.swap(dp2);
}
cout << dp[0] << '\n';
return 0;
}


Ex - Marquee (abc307 Ex)

题目大意

<++>

解题思路

<++>

神奇的代码


本文作者:~Lanly~

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

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

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