Codeforces Round #751 (Div. 2)
阿新 • • 發佈:2021-11-24
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;
}