Codeforces Round #751 (Div. 2)

Codeforces Round #751 (Div. 2)

题目链接

A - Two Subsequences

题目大意

T次操作, 给定一个字符串s, 求两个字符串ab, 满足:

  • ab都是来自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的数组ab, 开始处于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)

思路

对于两个人ij

  • 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;
}
posted @ 2021-11-24 20:08  哇唔?  阅读(58)  评论(0编辑  收藏  举报