AtCoder Beginner Contest 341
B - Foreign Exchange
难度: ⭐
题目大意
现在有1~n总共n种货币, 对于第i种货币小莫有Ai个, 并且可以花费Si张i货币兑换为Ti个i + 1货币; 请问最后小莫最多能用多少n货币;
解题思路
模拟就行, 能换就换;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e5 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, res;
int A[N];
signed main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> A[i];
for(int i = 1; i < n; i++){
int b, t;
cin >> b >> t;
A[i + 1] += t * (A[i] / b);
}
cout << A[n];
return 0;
}
C - Takahashi Gets Lost
难度: ⭐⭐
题目大意
给定一个n * m的网格, 其中'#'表示障碍物, 小莫位于某个'.'的位置, 小莫可以向上下左右移动, 现在给出小莫k次移动时每次的方向, 且每次前进一格; 请问该网格中有多少种满足该路程的起点;
解题思路
一个比较明显的bfs状态转移, 在队列中存当前应前进的方向和位置, 如果合法就前进并放进队列; 看看有多少个走完的即可;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 500 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, h, w, res;
char g[N][N], d[N];
struct node{
int x, y;
int idx;
};
queue<node> q;
int bfs(){
while(q.size()){
auto t = q.front();
q.pop();
if(t.idx > n){
res++;
continue;
}
if(d[t.idx] == 'L'){
if(g[t.x][t.y - 1] == '.'){
q.push({t.x, t.y - 1, t.idx + 1});
}
}
else if(d[t.idx] == 'R'){
if(g[t.x][t.y + 1] == '.'){
q.push({t.x, t.y + 1, t.idx + 1});
}
}
else if(d[t.idx] == 'U'){
if(g[t.x - 1][t.y] == '.'){
q.push({t.x - 1, t.y, t.idx + 1});
}
}
else if(d[t.idx] == 'D'){
if(g[t.x + 1][t.y] == '.'){
q.push({t.x + 1, t.y, t.idx + 1});
}
}
}
return res;
}
signed main(){
cin >> h >> w >> n;
for(int i = 1; i <= n; i++) cin >> d[i];
for(int i = 1; i <= h; i++){
for(int j = 1; j <= w; j++){
cin >> g[i][j];
if(g[i][j] == '.'){
q.push({i, j, 1});
}
}
}
cout << bfs();
return 0;
}
D - Only one of two
难度: ⭐⭐⭐
题目大意
给定n和m, 如果一个整数k是n和m其中一个的倍数, 那么k被称为一个好数; 注意只能是其中一个, 不能是n和m的公倍数; 请问第i小的好数是多少;
解题思路
一开始想找规律推公式, 结果发现不是很好搞; 然后发现题解的二分答案可太妙了; 我们可以求出1 ~ x范围内的好数的数量, 首先是x / n + x / m, 然后再减去2倍lcm(n, m)倍数的数量, 即 2 * (x / lcm(n, m)); 只要1 ~ x范围内有i个好数, 那么x就是我们要求的数, 注意x也得是一个好数;
但是我们知道对于x / n + x / m - 2 * (x / lcm(n, m)) = i这个式子, 因为都是整除, 所以会有多个数满足上式, 我们要找出其中的那个好数; 可以证得, 所以满足上式的整数中最小二的那个就是我们要找的好数;
注意: 因此在二分答案时要用mid = l + r >> 1;的写法, 这样是往小了找答案, 而mid = l + r + 1 >> 1是往大了找答案;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 2e5+ 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
int check(int u){
return u / n + u / m - 2 * (u / lcm(n, m));
}
signed main(){
cin >> n >> m >> k;
int l = 1, r = 2 * inf;
while(l < r){
int mid = l + r >> 1;
if(check(mid) < k) l = mid + 1;
else r = mid;
}
cout << r;
return 0;
}
E - Alternating String
难度: ⭐⭐⭐
题目大意
对于一个由01组成的序列, 只要满足任意相邻的两个数不同, 那么这就是一个好序列, 单独一个数字也是好序列; 现在给定一个序列S, 对其进行n次操作, 操作分为两种, 每种都会先选择一段区间;
一是把该区间的数翻转, 即0变成1, 1变成0; 二是询问该区间是否是一个好序列;
解题思路
很明显的一个线段树, 我感觉我写的挺臃肿的, 不过好在是一遍过了; 每个节点除了记录左右边界外, 还要记录该区间的左右边界的数字是多少, 然后用一个懒标记表示是否要翻转, 还有一个标记该区间是否是一个好序列;
pushup函数是状态的更新, 也是本题的难点吗, 有两个作用: 一是检查修改子区间后当前区间是否变成了好序列, 要求是左右区间都是好序列并且左区间的最后一个数和右区间的第一个数不同; 检查是否不同时要注意左右区间是否还有翻转的懒标记; 二是检查当其子区间翻转后, 当前区间左右边界的数字是否需要更改;
pushdown函数是传递懒标记, 记得传递后要更改当前区间左右边界的数字;
modify函数就是处理懒标记, 更改对应区间的翻转状态;
query函数是判断该区间是否是好序列, 和pushup函数不同, query函数可能只需要当前区间左区间的右区间是好序列即可, 而不用整个左区间都是好序列, 这取决于操作给出的区间范围; 只要某个区间在范围内且是好序列, 那么就返回true; 对于一个区间, 如果其左右区间都返回true, 那么就要再判断左区间的右边界和右区间的左边界是否不同; 而如果有一个是false就不行; 比较特殊的是给定的范围只包含了该区间的左区间或右区间, 这样就不需要额外的判断了, 只要返回true那就是true;
本题的难点还是在于是否及时更新区间的左右边界数字和处理懒标记; 细节还是看代码吧;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 5e5+ 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
string s;
struct node{
int l, r;
int fir, las;
bool rev, mask;
}tr[4 * N];
bool check(int u){
int a = tr[u << 1].las;
int b = tr[u << 1 | 1].fir;
if(tr[u << 1].rev) a = !a;
if(tr[u << 1 | 1].rev) b = !b;
if(a != b) return true;
else return false;
}
void pushup(int u){
if(tr[u << 1].mask && tr[u << 1 | 1].mask && check(u)){
tr[u].mask = true;
}
else tr[u].mask = false;
if(tr[u << 1].rev) tr[u].fir = !tr[u << 1].fir;
else tr[u].fir = tr[u << 1].fir;
if(tr[u << 1 | 1].rev) tr[u].las = !tr[u << 1 | 1].las;
else tr[u].las = tr[u << 1 | 1].las;
}
void pushdown(int u){
auto& root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if(root.rev){
left.rev = !left.rev;
right.rev = !right.rev;
root.rev = false;
root.fir = !root.fir;
root.las = !root.las;
}
}
void build(int u, int l, int r){
if(l == r){
tr[u] = {l, r, s[l] - '0', s[r] - '0', false, true};
}
else{
tr[u] = {l, r, s[l] - '0', s[r] - '0', false};
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].rev = !tr[u].rev;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid) modify(u << 1, l, r);
if(r > mid) modify(u << 1 | 1, l, r);
pushup(u);
}
}
bool query(int u, int l, int r){
if(tr[u].l >= l && tr[u].r <= r){
return tr[u].mask;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid){
bool t = query(u << 1, l, r);
if(!t) return false;
}
if(r > mid){
bool t = query(u << 1 | 1, l, r);
if(!t) return false;
}
if(l <= mid && r > mid){
if(check(u)) return true;
else return false;
}
else return true;
}
}
signed main(){
cin >> n >> m;
cin >> s;
s = ' ' + s;
build(1, 1, n);
for(int i = 1; i <= m; i++){
int a, b, c;
cin >> a >> b >> c;
if(a == 1){
modify(1, b, c);
}
else{
if(query(1, b, c)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
F - Breakdown
难度: ⭐⭐⭐⭐
题目大意
现有一个n个顶点m条边的无向图, 每个节点都有一个值W[i]; 其中某些顶点上有棋子; 我们可以进行以下操作:
首先, 选择图上的一枚棋子, 假设其在x节点上, 然后移除这枚棋子;
然后, 对于所有与节点x相邻的节点, 我们从中挑选若干个节点, 要求这些节点的W[i]的和要小于W[x], 然后给这些节点各自一枚棋子;
在一定操作数后图上的棋子一定会归零, 请求出最多能进行多少次这样的操作;
解题思路
首先是建图, 因为棋子的传递只能从W大的传递给小的, 所以可以把无向图转换为有向图, 对于W相等的边也不要连; 仔细阅读第二步操作, 会发现他和01背包非常相似, 在一定的W体积要求下装尽可能多的操作数; 因为数据范围支持O(n2), 所以我们可以在dfs里面套一个01背包; 并且为了进一步降低复杂度可以用记忆化搜索; 我们记录cnt[x]为从节点x开始扩散时最多的操作数是多少; 则x的父节点u的状态转移为dp[u][i] = max(dp[u][i], dp[u][i - W[x]] + cnt[x]); 然后我们记录初始点上的棋子数量A[i], 答案就是A[i] * cnt[i]的累加;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 5e3 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, res;
vector<int> adj[N];
vector<PII> v[N];
PII p[N];
int A[N], W[N], d[N], cnt[N];
int dp[N][N];
int dfs(int u){
int maxn = 0;
for(int x : adj[u]){
if(!cnt[x]) cnt[x] = dfs(x);
int num = 0;
for(int i = W[u] - 1; i >= W[x]; i--){
dp[u][i] = max(dp[u][i], dp[u][i - W[x]] + cnt[x]);
maxn = max(maxn, dp[u][i]);
}
}
return 1 + maxn;
}
signed main(){
IOS;
cin >> n >> m;
for(int i = 1; i <= m; i++){
int a, b;
cin >> a >> b;
p[i] = {a, b};
}
for(int i = 1; i <= n; i++) cin >> W[i];
for(int i = 1; i <= m; i++){
int a = p[i].first, b = p[i].second;
if(W[a] < W[b]) swap(a, b);
else if(W[a] == W[b]) continue;
adj[a].push_back(b);
d[b]++;
}
for(int i = 1; i <= n; i++){
if(d[i] == 0){
cnt[i] = dfs(i);
}
}
for(int i = 1; i <= n; i++) {
cin >> A[i];
res += A[i] * cnt[i];
}
cout << res;
return 0;
}