1. 程式人生 > 其它 >Codeforces Round #751 (Div. 2)

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;
}