【做题笔记 div2 A-E】Educational Codeforces Round 161 (Rated for Div. 2)

A. Tricky Template

给你一个整数 n n n 和三个字符串 a , b , c a, b, c a,b,c ,每个字符串由 n n n 个小写拉丁字母组成。

假设一个模板是由 n n n 个小写和/或大写拉丁字母组成的字符串 t t t 。如果从 1 1 1 n n n 的所有 i i i 都满足以下条件,则字符串 s s s 与模板 t t t 匹配:

  • 如果模板中第 i i i 个字母是小写字母,那么 s i s_i si 必须与 t i t_i ti 相同
  • 如果模板中的第 i i i 个字母是大写字母,那么 s i s_i si 必须与 t i t_i ti小写版本不同。例如,如果模板中有字母 “A”,则不能在字符串的相应位置使用字母 “a”。

因此,如果至少有一个 i i i 的条件不成立,字符串就与模板不匹配。

判断是否存在模板 t t t ,使得字符串 a a a b b b 与之匹配,而字符串 c c c 不匹配。

输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1000 1 \le t \le 1000 1t1000 ) - 测试用例的数量。

每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 20 1 \le n \le 20 1n20 ) - 给定字符串的长度。

接下来的三行包含字符串 a , b a, b a,b c c c 。每个字符串由 n n n 个小写拉丁字母组成。

输出

对于每个测试用例,如果存在模板 t t t ,且字符串 a a a b b b 与之匹配,而字符串 c c c 与之不匹配,则打印 “YES”。否则,打印 “否”。

样例

4
1
a
b
c
2
aa
bb
aa
10
mathforces
luckforces
adhoccoder
3
acc
abd
abc
YES
NO
YES
NO
YES
NO
YES
NO

思路:遍历字符串 c,如果对于 i ∈ [ 1 , n ] i\in [1,n] i[1,n]均有 c [ i ] = a [ i ] c[i]=a[i] c[i]=a[i]或者 c [ i ] = b [ i ] c[i]=b[i] c[i]=b[i] 成立,则模板 t 必然与 字符串 c匹配

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<functional>
#include<stack>
#include<list>
#include<iomanip>
#include<array>
#include<cassert>
#define fi first
#define se second
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { { -1, 0}, {1, 0}, {0, -1}, {0, 1} };
const int N = 1e5 + 5, M = 1e6 + 5;
const ll inf = (1ll << 50);
int mod = 1e9 + 7;
int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int t = 1;
    cin >> t;
    while(t--) {
        int n;
        cin >> n;
        string a, b, c;
        cin >> a >> b >> c;
        int f = 0;
        for(int i = 0;i < n;i++) {
            if(a[i] == c[i] || b[i] == c[i]) continue;
            f = 1;
        }
        if(f) cout << "YES\n";
        else cout << "NO\n";
    }
    return 0;
}

B. Forming Triangles

你有 n n n 根木棒,编号从 1 1 1 n n n 。第 i i i 根木棒的长度是 2 a i 2^{a_i} 2ai

你想从给定的 n n n 根小木棍中选出准确的 3 3 3 根小木棍,并用这些小木棍作为三角形的边,组成一个非退化三角形。如果一个三角形的面积严格地大于 0 0 0 ,那么这个三角形就叫做非退化三角形。

计算组成三角形的三元组的数量。注意,选择木条的顺序并不重要(例如,选择 1 1 1 -st、 2 2 2 -nd和 4 4 4 -th木条与选择 2 2 2 -nd、 4 4 4 -th和 1 1 1 -st木条是一样的)。

输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1t104 ) - 测试用例的数量。

每个测试用例由两行组成:

  • 第一行包含一个整数 n n n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \le n \le 3 \cdot 10^5 1n3105 );
  • 第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,,an ( 0 ≤ a i ≤ n 0 \le a_i \le n 0ain )( 0 ≤ a i ≤ n 0 \le a_i \le n 0ain ).

输入的附加限制:所有测试用例中 n n n 的总和不超过 3 ⋅ 1 0 5 3 \cdot 10^5 3105

输出

对于每个测试用例,打印一个整数–选择恰好是 3 3 3 根木棒以便用它们组成三角形的方法的数量。

样例

4
7
1 1 1 1 1 1 1
4
3 2 1 3
3
1 2 3
1
1
35
2
0
0

思路: 假设选择的三条边为 a ≤ b ≤ c a\le b\le c abc,因为木棒的长度为 2 a [ i ] 2^{a[i]} 2a[i],所以 c c c 一定等于 b b b ,否则不满足 2 a + 2 b > 2 c 2^a+2^b>2^c 2a+2b>2c,遍历数组枚举当前数为 b b b,用哈希表存储每个数 a [ i ] a[i] a[i]的出现次数 c n t a [ i ] cnt_{a[i]} cnta[i],如果现在选择的是第 j j j a [ i ] a[i] a[i],根据乘法原理,当前三元组贡献为 ( ∑ a [ k ] < a [ i ] c n t a [ k ] + j − 1 ) ∗ ( c n t a [ i ] − j ) (\sum_{a[k]<a[i]}cnt_{a[k]}+j-1)*(cnt_{a[i]}-j) (a[k]<a[i]cnta[k]+j1)(cnta[i]j)

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<functional>
#include<stack>
#include<list>
#include<iomanip>
#include<array>
#include<cassert>
#define fi first
#define se second
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { { -1, 0}, {1, 0}, {0, -1}, {0, 1} };
const int N = 1e5 + 5, M = 1e6 + 5;
const ll inf = (1ll << 50);
int mod = 1e9 + 7;
int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int t = 1;
    cin >> t;
    while(t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for(int& x : a) cin >> x;
        sort(a.begin(), a.end());
        map<int, int> mp;
        for(int i = 0;i < n;i++) {
            mp[a[i]]++;
        }
        ll ans = 0;
        ll cur = 0;
        for(auto& it : mp) {
            for(int j = 1;j <= it.second;j++) {
                ans += 1ll * (it.second - j) * cur;
                ++cur;
            }
        }
        cout << ans << "\n";
    }
    return 0;
}

C. Closest Cities

数线上有 n n n 座城市, i i i /th 座城市位于点 a i a_i ai 。城市的坐标按升序排列,所以是 a 1 < a 2 < ⋯ < a n a_1 \lt a_2 \lt \dots \lt a_n a1<a2<<an

两个城市 x x x y y y 之间的距离等于 ∣ a x − a y ∣ |a_x - a_y| axay

对于每个城市 i i i ,我们可以定义近的个城市 j j j ,即 i i i j j j 之间的距离不大于 i i i 和其他城市 k k k 之间的距离。例如,如果这些城市都位于点 [ 0 , 8 , 12 , 15 , 20 ] [0, 8, 12, 15, 20] [0,8,12,15,20] ,那么

  • 距离城市 1 1 1 最近的城市是城市 2 2 2
  • 距离城市 2 2 2 最近的城市是城市 3 3 3
  • 距离城市 3 3 3 最近的城市是城市 4 4 4
  • 离城市 4 4 4 最近的城市是城市 3 3 3
  • 离城市 5 5 5 最近的城市是城市 4 4 4

保证每个城市的最近城市都是唯一的。例如,城市不可能位于 [ 1 , 2 , 3 ] [1, 2, 3] [1,2,3] 点,因为这意味着城市 2 2 2 有两个最近的城市( 1 1 1 3 3 3 ,两者的距离都是 1 1 1 )。

您可以在城市之间旅行。假设您目前在城市 x x x 。那么您可以执行以下操作之一:

  • 前往任何其他城市 y y y ,支付 ∣ a x − a y ∣ |a_x - a_y| axay 金币;
  • 前往距离 x x x 最近的城市,支付 1 1 1 金币。

您会收到 m m m 个询问。在每个查询中,您将得到两个城市,您必须计算从一个城市到另一个城市所需的最少金币数。

输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1t104 ) - 测试用例的数量。

每个测试用例的格式如下:

  • 第一行包含一个整数 n n n ( 2 ≤ n ≤ 1 0 5 2 \le n \le 10^5 2n105 );
  • 第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,,an ( 0 ≤ a 1 < a 2 < ⋯ < a n ≤ 1 0 9 0 \le a_1 \lt a_2 \lt \dots \lt a_n \le 10^9 0a1<a2<<an109 );
  • 第三行包含一个整数 m m m ( 1 ≤ m ≤ 1 0 5 1 \le m \le 10^5 1m105 );
  • 然后是 m m m 行;其中的 i i i 行包含两个整数 x i x_i xi y i y_i yi 1 ≤ x i , y i ≤ n 1 \le x_i, y_i \le n 1xi,yin x i ≠ y i x_i \ne y_i xi=yi ),表示在 i i i /th查询中,你必须计算从城市 x i x_i xi 到城市 y i y_i yi 所需要花费的最少硬币数。

输入的其他限制

  • 在每个测试案例中,对于每个城市,最近的城市是唯一确定的;
  • 所有测试用例中 n n n 的总和不超过 1 0 5 10^5 105
  • 所有测试用例中 m m m 的总和不超过 1 0 5 10^5 105

输出

对于每个查询,打印一个整数 - 您必须花费的最少金币数。

样例

1
5
0 8 12 15 20
5
1 4
1 5
3 4
3 2
5 1
3
8
1
4
14

思路: 很显然的贪心策略,能前往距离最近的城市就采取第二种移动方式,如果不能则用第一种移动方式移动到相邻的下一个城市,确保能使用尽量多的第二种移动方式即可,因为移动可能是从左到右或者从右到左,因此需要预处理前后缀和。

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<functional>
#include<stack>
#include<list>
#include<iomanip>
#include<array>
#include<cassert>
#define fi first
#define se second
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { { -1, 0}, {1, 0}, {0, -1}, {0, 1} };
const int N = 1e5 + 5, M = 1e6 + 5;
const ll inf = (1ll << 50);
int mod = 1e9 + 7;
int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int t = 1;
    cin >> t;
    while(t--) {
        int n;
        cin >> n;
        vector<ll> v(n + 5), pre(n + 5), suf(n + 5);
        for(int i = 1;i <= n;i++) {
            cin >> v[i];
        }
        v[0] = v[n + 1] = 1ll << 50;
        for(int i = 1;i <= n;i++) {
            if(abs(v[i] - v[i - 1]) < abs(v[i] - v[i + 1])) {
                pre[i + 1] += abs(v[i] - v[i + 1]);
            }
            else {
                pre[i + 1]++;
            }
            pre[i] += pre[i - 1]; 
        }
        for(int i = n;i >= 1;i--) {
            if(abs(v[i] - v[i + 1]) < abs(v[i] - v[i - 1])) {
                suf[i - 1] += abs(v[i] - v[i - 1]);
            }
            else {
                suf[i - 1]++;
            }
            suf[i] += suf[i + 1]; 
        }
        int q;
        cin >> q;   
        while(q--) {
            int a, b;
            cin >> a >> b;
            if(a > b) {
                cout << suf[b] - suf[a] << "\n";
            }
            else cout << pre[b] - pre[a] << "\n";
        }
    }
    return 0;
}

D. Berserk Monsters

Monocarp 正在玩电脑游戏(又一次)。猜猜他在做什么?没错,杀怪。

一排有 n n n 个怪物,编号从 1 1 1 n n n 。其中 i i i-th 个怪物有两个参数:攻击值等于 a i a_i ai,防御值等于 d i d_i di 。为了杀死这些怪物,莫诺卡普给它们施放了狂暴咒语,因此它们会互相攻击,而不是攻击莫诺卡普的角色。

战斗由 n n n 个回合组成。每回合都会发生以下情况:

  • 首先,每个活着的怪物 i i i 都会对左边相邻活着的怪物(如果存在)和右边相邻活着的怪物(如果存在)造成 a i a_i ai 伤害;
  • 然后,每只在本回合中受到超过 d j d_j dj 伤害的活着的怪物 j j j 都会死亡。也就是说,当且仅当 j j j-th 个怪物的防御值 d j d_j dj 小于它在本轮受到的总伤害时,它才会死亡。

计算每一轮中死亡的怪物数量。

输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1t104 ) - 测试用例的数量。

每个测试用例由三行组成:

  • 第一行包含一个整数 n n n ( 1 ≤ n ≤ 3 ⋅ 1 0 5 1 \le n \le 3 \cdot 10^5 1n3105 );
  • 第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,,an ( 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109) ;
  • 第三行包含 n n n 个整数 d 1 , d 2 , … , d n d_1, d_2, \dots, d_n d1,d2,,dn ( 1 ≤ d i ≤ 1 0 9 1 \le d_i \le 10^9 1di109 )。

输入的附加限制:所有测试用例的 n n n 之和不超过 3 ⋅ 1 0 5 3 \cdot 10^5 3105

输出

对于每个测试案例,打印 n n n 个整数。 i i i 个整数应该等于在 i i i 个回合中死亡的怪物数量。

样例

3
5
3 4 7 5 10
4 9 1 18 1
2
2 1
1 3
4
1 1 2 4
3 3 4 2
3 1 0 0 0 
0 0 
1 1 1 0 

思路: 模拟题,可以用双向链表或者有序容器 s e t set set 模拟双向链表维护数组,每轮如果有怪物死亡,就将他们从链表中删除,同时用一个优先队列维护本轮怪物的死亡情况,具体地维护一个二元组 ( i d , f l a g ) (id, flag) (id,flag) f l a g = ( a [ i p r e ] + a [ i n e ] ) > b [ i ] flag=(a[i_{pre}] + a[i_{ne}]) > b[i] flag=(a[ipre]+a[ine])>b[i],即判断第 i i i 个怪物的前驱和后继攻击力之和是否大于其防御力,因为从链表中删除怪物会改变前驱和后继状态,所以对于每个需要删除的怪物 i i i ,删除后 i p r e i_{pre} ipre i n e i_{ne} ine 都需要更新 f l a g flag flag 并重新插入优先队列中,注意不需要删除队列中旧的二元组,用标记数组懒删除即可。

时间复杂度应该是 O ( n log ⁡ 2 2 n ) O(n\log^2_2n) O(nlog22n),但大概是跑不满的

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<functional>
#include<stack>
#include<list>
#include<iomanip>
#include<array>
#include<cassert>
#define fi first
#define se second
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { { -1, 0}, {1, 0}, {0, -1}, {0, 1} };
const int N = 1e5 + 5, M = 1e6 + 5;
const ll inf = (1ll << 50);
int mod = 1e9 + 7;
struct node {
    int id, turn;
    bool operator < (const node& t) const {
        return turn < t.turn;
    }
};
int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int t = 1;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> vis(n + 5), a(n + 5), b(n + 5);
        set<int> st;
        st.insert(0);
        st.insert(n + 1);
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            st.insert(i);
        }
        for (int i = 1; i <= n; i++) {
            cin >> b[i];
        }
        priority_queue<node> q;
        for(int i = 1;i <= n;i++) {
            int turn = (a[i - 1] + a[i + 1]) > b[i];
            q.push({i, turn});
        }
        for(int i = 1;i <= n;i++) {
            int ans = 0;
            vector<int> tmp;
            vector<int> upd;
            while(q.size() && q.top().turn == 1) {
                auto t = q.top();
                q.pop();
                if(vis[t.id]) continue;
                auto it = st.find(t.id);
                int pre = *prev(it), ne = *next(it);
                ++ans;
                vis[t.id] = 1;
                if(pre != 0) upd.push_back(pre);
                if(ne != n + 1) upd.push_back(ne);
                tmp.push_back(t.id);
            }
            for(int v : tmp) st.erase(v);
            for(int v : upd) {
                auto it = st.find(v);
                int pre = *prev(it), ne = *next(it);
                int turn = (a[pre] + a[ne]) > b[v];
                q.push({v, turn});
            }
            cout << ans << " ";
        }
        cout << "\n";
    }
    return 0;
}

E. Increasing Subsequences

让我们回顾一下,数组 a a a 的递增子序列是指在不改变其余元素顺序的情况下,通过移除某些元素而得到的序列,并且其余元素是严格递增的(即 a b 1 < a b 2 < ⋯ < a b k a_{b_1} < a_{b_2} < \dots < a_{b_k} ab1<ab2<<abk and b 1 < b 2 < ⋯ < b k b_1 < b_2 < \dots < b_k b1<b2<<bk )。注意,空子序列也是递增的。

给你一个正整数 X X X 。你的任务是找出一个长度为**最多 200 200 200 **的整数数组,使得它有恰好 X X X 个递增的子序列,或者报告说没有这样的数组。如果有多个答案,你可以打印其中任何一个。

如果两个子序列由相同的元素组成,但对应数组中的不同位置,则认为它们是不同的(例如,数组 [ 2 , 2 ] [2, 2] [2,2] 有两个不同的子序列等于 [ 2 ] [2] [2] )。

输入

第一行包含一个整数 t t t ( 1 ≤ t ≤ 1000 1 \le t \le 1000 1t1000 ) - 测试用例的数量。

每个测试用例的唯一一行包含一个整数 X X X ( 2 ≤ X ≤ 1 0 18 2 \le X \le 10^{18} 2X1018 )。

输出

对于每个查询,打印其答案。如果无法找到所需的数组,则在第一行打印 -1。否则,在第一行打印正整数 n n n - 数组的长度。在第二行打印 n n n 个整数–所需的数组本身。如果有多个答案,可以打印任意一个。数组的所有元素都应在 [ − 1 0 9 ; 1 0 9 ] [-10^9; 10^9] [109;109] 范围内。

样例

4
2
5
13
37
1
0
3
0 1 0
5
2 2 3 4 2
7
-1 -1 0 0 2 3 -1

思路: 以74为例, 74 = 2 6 + 2 3 + 2 1 74=2^6+2^3+2^1 74=26+23+21,对于一个长度为 i i i 的严格递增序列 a a a,在末尾插入一个大于 a [ i ] a[i] a[i] 的数会产生 2 i 2^i 2i 的贡献,那么
2 1 = 2 0 + 2 0 2 3 = 2 1 + 2 1 + 2 2 2 6 = 2 3 + 2 3 + 2 4 + 2 5 2^1=2^0+2^0\\ 2^3=2^1+2^1+2^2\\ 2^6=2^3+2^3+2^4+2^5\\ 21=20+2023=21+21+2226=23+23+24+25
以每个二进制位为1的bit分段即可,每段的开头用一个较大的数用于每段贡献的累积进位,下图直观展示这个过程

myplot

注意空的段贡献1,所以先将x -= 1

#include<iostream>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<functional>
#include<stack>
#include<list>
#include<iomanip>
#include<array>
#include<cassert>
#define fi first
#define se second
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const int dir[4][2] = { { -1, 0}, {1, 0}, {0, -1}, {0, 1} };
const int N = 1e5 + 5, M = 1e6 + 5;
const ll inf = (1ll << 50);
int mod = 1e9 + 7;
int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(0);
    int t = 1;
    cin >> t;
    while(t--) {
        ll x;
        cin >> x;
        vector<int> v;
        --x;
        int cnt = __builtin_popcountll(x);
        vector<int> res;
        int a = 0, b = 1e9;
        res.push_back(b--);
        for(int i = 0;i < 63;i++) {
            if(x & (1ll << i)) {
                --cnt;
                if(cnt) res.push_back(b--);
            }
            if(cnt == 0) break;
            res.push_back(a++);
        }
        cout << res.size() << "\n";
        for(int v : res) cout << v << " ";
        cout << "\n";
    }
    return 0;
}
  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值