CodeCraft-22 and Codeforces Round #795 (Div. 2) A - E
感觉这一场的题目真的非常棒,找时间一定要把 E 也给补了(除非看完答案还是不会),虽然现在还没看,但是前几题的质量让我有继续做下去的欲望
更新:E 题已经补了,感觉还挺不错
A. Beat The Odds
要么全是奇数,要么全是偶数,看着哪个少就删哪个
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int l = 0, r = 0;
int n;
cin >> n;
for(int i=0; i<n; i++)
{
int x;
cin >> x;
if(x & 1) l++;
else r++;
}
cout << min(l, r) << endl;
}
return 0;
}
B. Shoe Shuffling
从最大的开始想,显然只能和自己一样的换,因此可以推出只能和自己一样的换
先找出 -1 的情况:有一种鞋子出现的频率为 1
接着构造就好了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
int num[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
int ans = n;
for(int i=1; i<=n; i++) cin >> num[i];
num[0] = num[1];
num[n + 1] = num[n] + 1;
int pre = 0;
for(int i=1; i<= n + 1; i++)
{
if(num[i] == num[i-1]) pre++;
else
{
ans = min(ans, pre);
pre = 1;
}
}
if(ans <= 1) cout << -1 << endl;
else
{
int cur = 1;
for(int i=1; i<=n; i++)
{
if(i != 1) cout << " ";
if(num[i+1] != num[i])
{
cout << cur;
cur = i + 1;
}
else cout << i + 1;
}
cout << endl;
}
}
return 0;
}
C - Sum of Substrings
优先级:
-
最后一个位置 —— 只会产生一个个位数
-
第一个位置 —— 只会产生一个十位数
-
其他位置 —— 生成一个个位数、一个十位数
因此就考虑把最靠近末尾的 1 先挪到末尾,再把最靠近开头的 1 挪到开头
注意不要把已经挪到末尾的 1 挪到最开头去
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
int num[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int n, m;
cin >> n >> m;
string s;
cin >> s;
if(s[n-1] == '0')
{
int f = n - 1;
for(int i=n-1; i>=0; i--)
{
if(s[i] == '1')
{
f = i;
break;
}
}
if(n - f - 1 <= m)
{
m -= n - f - 1;
swap(s[n-1], s[f]);
}
}
if(s[0] == '0' && m)
{
int f = 0;
for(int i=0; i<n; i++)
{
if(s[i] == '1')
{
f = i;
break;
}
}
if(f <= m && f != n -1)
{
swap(s[0], s[f]);
}
}
int ans = 0;
int f = 0;
for(int i=1; i<n-1; i++)
f += s[i] - '0';
ans += (s[0] - '0') * 10 + (s[n-1] - '0') + f * 11;
cout << ans << endl;
}
return 0;
}
D - Max GEQ Sum
单调栈 + 线段树
这题非常值得
思路:
假设我们此时的最大值为 \(a_i\),那么我们考虑不等式是否成立时,就应该考虑一个最大的区间 \([l,r]\),在这里找有没有不符合不等式的情况出现
最大区间 \([l, r]\):\(a_{l - 1} > a_i\) 且 \(a_{r + 1} > a_i\)
如何找到不符合不等式的情况:
\(sum(l,r) > a_i\)
-> \(sum(l, i-1) + sum(i+1, r) > 0\)
因为我们只要在 \([l, r]\) 上找到一个任意的包含 \(a_i\) 的区间,使得其和大于 \(a_i\) 即可,因此我们只需要考虑其两边任意一个区间大于 0 即可
-> \(sum(l,i-1) > 0\) 或 \(sum(i+1,r) > 0\)
实现:
- 找到区间 \([l, r]\),即找到每个 \(a_i\) 左右两边第一个大于 \(a_i\) 的数
这里考虑使用单调栈,栈底到栈顶,从大到小维护,碰到比当前数字小的就弹出
- 找不等式不成立的情况
这里我们改成 \(sum(l,i) > a_i\) 或 \(sum(i,r) > a_i\) 来实现
我们考虑区间和的时候使用前缀和 \(S\) 维护,以 \(sum(l,i) > a_i\) 为例:
\(sum(j,i) = S_i - S_{j-1}\) \((l \le j \le i)\)
不难发现,在查找的区间中,\(S_i\) 是不变的,因此我们可以转化成为在 \([l-1, i-1]\) 中,找一个 \(S\) 的最小值
这个查找可以考虑用线段树来实现,同理对于右边的情况可以用查找最大值来实现
单调栈:\(O(n)\)
总体线段树查询:\(O(nlogn)\)
时间复杂度:\(O(nlogn)\)
挺可惜没想到线段树 + 前缀和维护,想半天一直卡在找的上面
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>
#include <functional>
#include <map>
#include <set>
#include <cmath>
#include <cstring>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
const ll maxn = 2e5 + 10;
const ll inf = 1e17 + 10;
ll num[maxn], sum[maxn];
int pre_l[maxn], pre_r[maxn];
void st_solve(int n)
{
stack<int>st;
st.push(0);
num[0] = num[n+1] = inf;
for(int i=1; i<=n; i++)
{
while(num[st.top()] <= num[i]) st.pop();
pre_l[i] = st.top();
st.push(i);
}
while(st.size()) st.pop();
st.push(n+1);
for(int i=n; i>=1; i--)
{
while(num[st.top()] <= num[i]) st.pop();
pre_r[i] = st.top();
st.push(i);
}
num[0] = num[n+1] = 0;
}
struct node
{
ll maxx, minn;
}tr[maxn << 2];
void push_up(int now)
{
tr[now].maxx = max(tr[now << 1].maxx, tr[now << 1 | 1].maxx);
tr[now].minn = min(tr[now << 1].minn, tr[now << 1 | 1].minn);
}
void build(int now, int l, int r)
{
if(l == r)
{
tr[now].maxx = tr[now].minn = sum[l];
return;
}
int mid = l + r >> 1;
build(now << 1, l, mid);
build(now << 1 | 1, mid + 1, r);
push_up(now);
}
ll query_min(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R)
return tr[now].minn;
int mid = l + r >> 1;
ll ans = inf;
if(L <= mid) ans = query_min(now << 1, l, mid, L, R);
if(R > mid) ans = min(ans, query_min(now << 1 | 1, mid + 1, r, L, R));
return ans;
}
ll query_max(int now, int l, int r, int L, int R)
{
if(L <= l && r <= R)
return tr[now].maxx;
int mid = l + r >> 1;
ll ans = -inf;
if(L <= mid) ans = query_max(now << 1, l, mid, L, R);
if(R > mid) ans = max(ans, query_max(now << 1 | 1, mid + 1, r, L, R));
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
for(int i=1; i<=n; i++)
{
cin >> num[i];
sum[i] = sum[i-1] + num[i];
}
st_solve(n);
build(1, 0, n);
int f = 1;
for(int i=1; i<=n && f; i++)
{
int l = pre_l[i];
int r = pre_r[i];
ll l_val = query_min(1, 0, n, l, i - 1);
ll r_val = query_max(1, 0, n, i, r - 1);
if(max(sum[i] - l_val, r_val - sum[i-1]) > num[i]) f = 0;
}
if(f) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
E. Number of Groups
并查集 + set
我觉得这个题 cf 自己的题解写的挺好的
把所有的区间段化成左端点和右端点的方式,然后从左到右逐个遍历点,这样就可以降维
如果当前访问到一个左端点,则加入到 set 当中,表示现在是处于这个区间段上的,当然当前的点可以处于多个区间段内
如果当前访问到一个右端点,就直接删除掉 set 中,这个段的左端点,表示现在已经不在这个区间段内了
在访问左端点的时候,当前线段显然于存在 set 中的所有线段有交点,这样就可以直接开始用并查集维护了
如果都是重合到一起,显然操作还是 \(O(n^2)\) 的
其实我们只需要留下一个能合并的区间中,右端点最远的作为这个集合的代表,提供给后面的线段进行合并,其他的都是可以删掉的,这样建图就能保证是在 \(O(n)\) 的了
然后有两种颜色,所以要用两个 set 去分别维护两种颜色,操作都和上述的表示一致
复杂度为 \(O(nlogn)\)
#include <iostream>
#include <cstdio>
#include <set>
#include <map>
#include <vector>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define pii pair<int, int>
const int maxn = 1e5 + 10;
int top[maxn], vis[maxn << 1];
struct node
{
int t, l, r, id;
}seg[maxn];
int query(int x)
{
return x == top[x] ? x : top[x] = query(top[x]);
}
inline void comb(int x, int y)
{
int fx = query(x);
int fy = query(y);
if(fx != fy)
top[fx] = fy;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
map<int, vector<pii>>mp;
for(int i=0; i<=n; i++) top[i] = i;
for(int i=1; i<=n; i++)
{
int t, l, r;
cin >> t >> l >> r;
seg[i] = {t, l, r, i};
mp[l].push_back({0, i});
mp[r].push_back({1, i});
}
set<int>s[2];
for(auto it=mp.begin(); it!=mp.end(); it++)
{
sort(it->second.begin(), it->second.end());
for(int i=0; i<it->second.size(); i++)
{
pii now = it->second[i];
node p = seg[now.second];
if(now.first == 0)
{
if(s[p.t ^ 1].size())
{
int way = -1, nex = 0;
for(auto j=s[p.t ^ 1].begin(); j!=s[p.t ^ 1].end(); j++)
{
comb(*j, now.second);
vis[*j] = 0;
if(seg[*j].r > way)
{
way = seg[*j].r;
nex = *j;
}
}
s[p.t ^ 1].clear();
s[p.t ^ 1].insert(nex);
vis[nex] = 1;
}
s[p.t].insert(p.id);
vis[p.id] = 1;
}
else
{
if(vis[now.second])
{
vis[now.second] = 0;
s[p.t].erase(p.id);
}
}
}
}
int ans = 0;
for(int i=1; i<=n; i++) ans += top[i] == i;
cout << ans << endl;
}
return 0;
}