Codeforces Round #751 (Div. 2)
Codeforces Round #751 (Div. 2)
A - Two Subsequences
题目大意
T次操作, 给定一个字符串s
, 求两个字符串a
和b
, 满足:
a
和b
都是来自s
的字串a
的长度尽可能小b
的长度尽可能大a
的字典序尽可能小
思路
- 想要满足条件二 三, 必然是
a
只有一个字符, 其他所有字符均在b
中 - 加上条件四, 就是将
s
中最小的字符赋给a
遍历s字符串, 寻找最小的字符, 输出最小的字符后, 在跳过该字符输出其他字符
代码
代码
#include <iostream>
#include <cstring>
using namespace std;
bool st[30];
int main() {
int T;
cin >> T;
while (T --) {
string a;
cin >> a;
memset(st, false, sizeof st);
for (auto i : a) st[i - 'a'] = true;
char t = 0;
for (int i = 0; i < 26; i ++)
if (st[i]) {
t = 'a' + i;
break;
}
cout << t << ' ';
for (auto i : a)
if (i == t) {
t = 0;
}
else {
cout << i;
}
cout << endl;
}
}
B - Divine Array
题目大意
T次操作, 给定一个长度为n
的数组, 然后是q
次询问, 每次输出经过k
次转换之后位置为x
的数组元素
每次操作为 数组元素转换为该元素在数组中出现的次数
思路
- 2e3的数组, 1e6次的询问, 需要进行一次初始化然后每次询问查表即可
- 首先是需要知道元素在数组中出现过的次数, 根据次数修改元素内容
建立二维数组存储第i
次转换的第j
个元素的内容, 每次转换遍历一次上一层统计次数, 然后在当前层记录结果
代码
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2010;
int n, m;
int b[N][N]; // 第i个元素的第j次
int cnt[N];
int main() {
int T;
cin >> T;
while (T --) {
cin >> n;
for (int i = 1; i <= n; i ++) cin >> b[i][0];
for (int i = 1; i <= n; i ++) {
memset(cnt, 0, sizeof cnt);
for (int j = 1; j <= n; j ++) cnt[b[j][i - 1]] ++;
for (int j = 1; j <= n; j ++) b[j][i] = cnt[b[j][i - 1]];
}
cin >> m;
while (m --) {
int x, k;
cin >> x >> k;
k = min(k, n);
cout << b[x][k] << endl;
}
}
}
C - Array Elimination
题目大意
T次操作, 给定一个长度为n
的数组, 问是否存在一个数k
, 满足
- 在数组中恰好选取
k
的数字 - 这
k
个数字减去他们的按位与&
- 经过任意次操作之后使得数组全部为
0
思路
- 涉及到按位与
&
, 那必然要用二进制的思维来判断 - 对于二进制中的某一位, 如果想要将所有的都转换为
0
, 那么必然是选择这一位中1
的个数的因子才可以 - 那么对于多个位数之间, 只能选择他们共有的因子才可以, 即最大公因数
统计每一位中1
的个数, 然后求其的最大公约数, 最后输出最大公约数的所有因子
代码
代码
#include <iostream>
#include <cstring>
#include <set>
using namespace std;
const int N = 2e5 + 10;
int n;
int a[N];
int cnt[50];
int p[N], idx;
bool st[N];
void init();
int lcm(int a, int b);
int gcd(int a, int b);
void check(int n);
int main() {
init();
int T;
cin >> T;
while (T --) {
memset(cnt, 0, sizeof cnt);
set<int> s;
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
for (int j = 0; j < 31; j ++)
if (a[i] >> j & 1) {
cnt[j] ++;
}
}
for (int i = 0; i < 31; i ++)
if (cnt[i]) {
s.insert(cnt[i]);
}
if (s.empty()) {
for (int i = 1; i <= n; i ++) cout << i <<' ';
cout << endl;
continue;
}
int ans = -1;
for (auto i : s) {
if (ans == -1) ans = i;
else ans = gcd(ans, i);
}
check(ans);
cout << endl;
}
}
void check(int n) {
for (int i = 1; i <= n; i ++) if (n % i == 0) cout << i << ' ';
}
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
void init() {
for (int i = 2; i < N; i ++) {
if (!st[i]) p[idx ++] = i;
for (int j = 0; p[j] * i < N; j ++) {
st[p[j] * i] = true;
if (i % p[j] == 0) break;
}
}
}
D - Frog Traveler
题目大意
给定长度为n
的数组a
和b
, 开始处于n
的位置, 问经过最少多少次转换之后可以到达0
转换为 每次可以从下标i
转换到下标为j
的位置, j满足j <= i && j >= i - a[i]
, 并且最后j
要变为j + b[j]
思路
设f[i]
为跳到i
的最小步数, 每次转移先减去b[i]
再转移
求最小值可以用线段树来优化
代码
代码
#include<cstdio>
using namespace std;
const int N = 300010;
int n, now;
int v[N], a[N], b[N], lst[N];
struct Tree {
#define ls (x * 2)
#define rs x * 2 + 1
int s[N << 2], lazy[N << 2];
void push_up(int x) {
if(v[s[ls]] < v[s[rs]]) s[x] = s[ls];//因为要存路径,所以更改存的方式
else s[x] = s[rs];
return;
}
void get(int x, int y) {
if(v[s[x]] > v[y]) s[x] = y;
if(v[lazy[x]] > v[y]) lazy[x] = y;
return;
}
void build(int x, int l, int r) {
s[x] = lazy[x] = n + 2;
if(l == r) return;
int mid = l + r >> 1;
build(ls,l, mid);
build(rs,mid+1, r);
return;
}
void push_down(int x) {
if(lazy[x] != n+2){
get(ls,lazy[x]);
get(rs,lazy[x]);
lazy[x] = n + 2;
}
return;
}
void add(int x, int L, int R, int l, int r, int y) {
if(L == l && R == r){
get(x, y);
return;
}
push_down(x);
int mid = L + R >> 1;
if(r <= mid) add(ls, L, mid, l, r, y);
else if(l > mid) add(rs,mid+1, R, l, r, y);
else add(ls, L, mid, l, mid, y), add(rs,mid + 1, R,mid+1, r, y);
push_up(x);
}
int ask(int x, int l, int r, int y) {
if(l == r) return s[x];
push_down(x);
int mid = (l + r) >>1;
if(y <= mid)return ask(ls, l, mid, y);
else return ask(rs,mid+1, r, y);
}
}T;
void dfs(int x){
if(x == n) return;
dfs(lst[x]);
printf("%d ", x - 1);
}
int main() {
scanf("%d", &n); n ++;
for(int i = 2; i <= n; i ++) scanf("%d", a + i);
for(int i = 2; i <= n; i ++) scanf("%d", b + i);
T.build(1,1, n);
v[n + 2] = 1e9;
v[n + 1] = 0;
T.add(1,1, n, n, n,n+1);
for(int i = n; i > 1; i --){
lst[i] = T.ask(1,1, n, i);
v[i] = v[lst[i]] + 1;
now = i + b[i];//往后bi步
T.add(1,1, n,now-a[now], now, i);
}
lst[1] = T.ask(1,1, n,1);
v[1] = v[lst[1]] + 1;
if(lst[1] == n+2){
puts("-1");
return 0;
}
printf("%d\n", v[1] - 1);
now=1;
dfs(1);
return 0;
}
E - Optimal Insertion
题目大意
T次操作, 给定一个长度为n
的数组a
和长度为m
的数组b
, 求数组c
的逆序对的数量
数组c
的定义:
在数组a
不变的情况下, 以任意顺序将数组b
中的元素任意地插入a
中, 得到长度为n + m
的数组c
思路
把a数组、b数组都按值排序。从小到大枚举b,同时维护一棵线段树,节点i表示b插入ai 与 ai - 1之间时的代价,一开始a都没有加进来,意味着当前每个a都是大于b的,那么节点i的值 = i-1。然后b增大了,一些大于b的元素变成了等于b,那么把这些元素往后的插入空隙在线段树上对应节点值-1;一些等于b的元素变成了小于b,那么这些元素往前的插入空隙在线段树上对应节点值+1,更改完后查询整棵线段树的最小值就是当前这个b插入最优位置产生的逆序对数。
代码
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 1000100
#define mp make_pair
#define fs first
#define sn second
using namespace std;
ll t,n,m,w,x,nm,ans,c[N];
pair<ll,pair<ll,ll> >a[N<<2];
struct Tree
{
#define ls x*2
#define rs x*2+1
ll s[N<<2],lazy[N<<2];
void push_up(ll x)
{
s[x]=min(s[ls],s[rs]);
return;
}
void get(ll x,ll y)
{
s[x]+=y;
lazy[x]+=y;
return;
}
void push_down(ll x)
{
if(lazy[x]){
get(ls,lazy[x]);
get(rs,lazy[x]);
lazy[x]=0;
}
return;
}
void build(ll x,ll l,ll r)
{
s[x]=0;
lazy[x]=0;
if(l==r)return;
ll mid=l+r>>1;
build(ls,l,mid);
build(rs,mid+1,r);
return;
}
void add(ll x,ll L,ll R,ll l,ll r,ll y)
{
if(L==l&&R==r){
get(x,y);
return;
}
push_down(x);
ll mid=L+R>>1;
if(r<=mid)add(ls,L,mid,l,r,y);
else if(l>mid)add(rs,mid+1,R,l,r,y);
else add(ls,L,mid,l,mid,y),add(rs,mid+1,R,mid+1,r,y);
push_up(x);
}
ll ask(ll x,ll l,ll r,ll y)
{
if(l==r)return s[x];
push_down(x);
ll mid=l+r>>1;
if(y<=mid)return ask(ls,l,mid,y);
else return ask(rs,mid+1,r,y);
}
}T;
void add(ll x)
{
for(;x<=n;x+=x&-x)
c[x]++;
return;
}
ll ask(ll x)
{
ll sum=0;
for(;x;x-=x&-x)
sum+=c[x];
return sum;
}
int main()
{
scanf("%lld",&t);
while(t--){
scanf("%lld%lld",&n,&m);
w=0;
nm=n+1;
ans=0;
T.build(1,1,nm);
for(ll i=1;i<=n;++i){
c[i]=0;
scanf("%lld",&x);
T.add(1,1,nm,i+1,nm,1);
a[++w]=mp(x,mp(0,i));
a[++w]=mp(x,mp(1,i));//相同的数字不计逆序对
}
for(ll i=1;i<=m;++i){
scanf("%lld",&x);
a[++w]=mp(x,mp(1,0));
}
sort(a+1,a+1+w);
for(ll i=1;i<=w;++i){
if(!a[i].sn.fs){
T.add(1,1,nm,a[i].sn.sn+1,nm,-1);
ans+=ask(n)-ask(a[i].sn.sn);//计算a中的贡献
add(a[i].sn.sn);
}
else if(!a[i].sn.sn){
ans+=T.s[1];
}
else{
T.add(1,1,nm,1,a[i].sn.sn,1);
}
}
printf("%lld\n",ans);
}
return 0;
}
F - Difficult Mountain
题目大意
给定人数n
和山峰高度d
, 以下n'行给定s[i]
和a[i]
, 问最多能使多少人完成登山
条件
- 只有
s >= d
的人才能完成登山 - 这个人登山完毕后, 山的高度会变为
max(a, d)
思路
对于两个人i
和j
s[i] < a[j]
如果把 j jj 放在前面,j jj 能成功登山,i ii 一定无法登山,应该先让 i ii 进行尝试a[i] < s[j]
j jj 的能力比 i ii 强,放在后面一定不劣s[i] < s[j]
同理第二条, 一定不劣a[i] < a[j]
j 成功登山后 i ii 一定无法登山,应该把 i ii 放在前面先进行尝试
所以对所有人进行排序, 以max(s[i], a[i])
进行升序排序,如果相等按照s[i]
排序。排序后,对每个登山者进行判断即可。
代码
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
const int N = 5e5 + 10;
int n, d;
PII a[N];
int main() {
cin >> n >> d;
for (int i = 1; i <= n; i ++) cin >> a[i].first >> a[i].second;
sort(a + 1, a + n + 1, [](PII a, PII b) {
if (max(a.first, a.second) == max(b.first, b.second)) return a.first < b.first;
return max(a.first, a.second) < max(b.first, b.second);
});
int ans = 0;
for (int i = 1; i <= n; i ++)
if (d <= a[i].first) {
ans ++;
d = max(a[i].second, d);
}
cout << ans << endl;
}