比赛链接:https://codeforces.com/contest/1213
A. Chips Moving
解题心得:统计一下奇数更少还是偶数更少,输出少的那个的个数就行了。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+1100;
int n, num[maxn];
int main() {
int cnt1, cnt2;//记录奇数个数和偶数个数
cnt1 = cnt2 = 0;
scanf("%d", &n);
for(int i=1;i<=n;i++) {
scanf("%d", &num[i]);
if(num[i]&1) cnt1++;
else cnt2++;
}
printf("%d\n", min(cnt1, cnt2));
}
B. Bad Prices
题意:现在有一个商品,总共有 n n n天,商品每一天有一个价格,如果今天价格位 T i Ti Ti,但是如果在之后有一个价格小于 T i Ti Ti,则今天为bad day,问n天中有多少个bad day。
解题心得:直接从 n n n天开始往前更新最低价格,如果当前价格有大于最低价格的则答案加一,否则更新最低价格。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+1100;
int n, num[maxn];
int main() {
int t; scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &num[i]);
int cnt = 0, Min = INT_MAX;
for (int i = n; i >= 1; i--) {
if (num[i] <= Min) Min = num[i];
else cnt++;
}
printf("%d\n", cnt);
}
return 0;
}
C. Book Reading
题意:现在有一本书 n n n页,每次可以看 m m m页直到看完整本书,当每一个 m m m的 p i p_{i} pi倍有一个 k i = ( m ∗ p i ) % 10 k_{i}=(m*p_{i})\%10 ki=(m∗pi)%10,现在你需要的到 ∑ k i \sum k_{i} ∑ki。
解题心得:直接找一个循环节为 10 10 10的 k i k_{i} ki的每一个值,然后每一个所有能够得到完整循环节直接算出来,不能得到的单个加一下。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+1100;
vector <ll> ve;//存储循环节中的每一个数
int main() {
// freopen("1.in.txt", "r", stdin);
int t; scanf("%d", &t);
while(t--) {
ll n, m; scanf("%lld%lld", &n, &m);
if(m > n) {
puts("0");
continue;
}
ve.clear();
ll cnt = n/m;//cnt个完成的循环节
ll sum = 0;//一个循环节的和
for(int i=1;i<=10;i++) {
ve.push_back((m*i)%10);
sum += (m*i)%10;
}
ll ans = cnt/10*sum;
cnt = cnt%10;//剩下的单独算一下就行了
for(int i=0;i<cnt;i++)
ans += ve[i];
printf("%lld\n", ans);
}
return 0;
}
D2. Equalizing by Division (hard version)
题意:现在有一个长度为 n n n的数列,你可以将数列中的每一个多次数除以 2 2 2并且向下取整,每当一个数除以 2 2 2并下向下取整看作一个操作,问你至少需要多少个操作可以让数列中至少有 k k k个数相同。
解题心得:首先将整个数列按升序排列,然后从小到大预处理出不大于 x ( x ∈ [ 1 , n ] ) x(x \in [1,n]) x(x∈[1,n])的数的个数。然后从 1 1 1开始枚举每一个数作为满足 k k k个数的数答案,假设枚举当前的数为 z z z,则可能得到 z z z的数为 [ z ∗ 2 , z ∗ 2 + 1 ] , [ z ∗ 2 ∗ 2 , ( z ∗ 2 + 1 ) ∗ 2 + 1 ] . . . [z*2, z*2+1], [z*2*2, (z*2+1)*2+1]... [z∗2,z∗2+1],[z∗2∗2,(z∗2+1)∗2+1]...,统计一下每段区间内可以在数列中找到的个数,由于之前已经预处理出来,寻找个数的复杂度就是 O ( 1 ) O(1) O(1),枚举加上每次按照一个类似倍增的寻找复杂度总共是 O ( n ∗ l o g n ) O(n*logn) O(n∗logn)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+1100;
int n, k, num[maxn], sum[maxn];//sum[i]表示不大与i的数有多少个,这里用的是树状数组计数
int lowbit(int x) {
return x&-x;
}
void add(int x) {
if(x <= 0) return ;
while(x < maxn) {
sum[x]++;
x += lowbit(x);
}
}
int get_sum(int x) {
if(x <= 0) return 0;
int Sum = 0;
while(x) {
Sum += sum[x];
x -= lowbit(x);
}
return Sum;
}
int main() {
// freopen("1.in.txt", "r", stdin);
scanf("%d%d",&n, &k);
int ans = 0;
for(int i=1;i<=n;i++) {
scanf("%d", &num[i]);
add(num[i]);
int z = num[i];
while(z) {//统计若最终的答案数字为0需要的次数
ans++;
z /= 2;
}
}
sort(num+1, num+1+n);
for(int i=1;i<=num[n];i++) {
int cnt = get_sum(i) - get_sum(i-1);
int need = k - cnt, p = 1;
if(need <= 0) {
ans = 0;
break;
}
int l = i*2, r = i*2+1, temp = 0;//类似倍增每一个区间
while(l <= num[n]) {
ll now = get_sum(r) - get_sum(l-1);
if(now < need) {
temp += now*p;
need -= now;
} else {
temp += need*p;
need = 0;
break;
}
l = l*2;
r = r*2+1;
p++;
}
if(need == 0) ans = min(ans, temp);
}
printf("%d\n", ans);
return 0;
}
E. Two Small Strings
解题心得:其实就是将 a b c abc abc的全排列然后凑成 3 ∗ n 3*n 3∗n的长度,和分别将 a b c abc abc放在一起调换位置就是所有的情况,然后将所有结果再检验一下就行了,按情况来说不可能有 N O NO NO。直接莽一波,单车变摩托。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000;
string tot = "abc", x1, x2;
vector <string> ve;
int n;
void get_string() {
do{
string temp;
for(int i=0;i<n;i++) temp += tot;
ve.push_back(temp);
temp.clear();
for(int i=0;i<3;i++) {
for(int j=0;j<n;j++) temp += tot[i];
}
ve.push_back(temp);
} while(next_permutation(tot.begin(), tot.end()));
}
int main() {
// freopen("1.in.txt", "r", stdin);
scanf("%d", &n);
cin>>x1>>x2;
get_string();
for(int i=0;i<ve.size();i++) {
string now = ve[i];
if(now.find(x1) == -1 && now.find(x2) == -1) {
puts("YES");
cout<<now;
return 0;
}
}
puts("NO");
return 0;
}
F. Unstable String Sort
解题心得:
- 假设在第一个数列中 s [ 3 ] < = s [ 5 ] s[3]<=s[5] s[3]<=s[5],但在第二个数列中 s [ 5 ] < = s [ 3 ] s[5]<=s[3] s[5]<=s[3],这就可以直接得到 s [ 5 ] = s [ 3 ] s[5]=s[3] s[5]=s[3],这个时候若是 5 5 5和 3 3 3在数列中的位置不相邻,则从 5 5 5的位置到 3 3 3的位置这段区间内的所有 s [ i ] s[i] s[i]都相等。
- 发现了以上规律这个题就有思路了,肯定是先相互对比第一个数列和第二个数列,将 s [ i ] s[i] s[i]相等的区间全找出来,这里可以用并查集操作,若两个区间有相交的部分,那么这两个区间合并成一个区间,最后看有多少个区间,若 m m m大于区间个数肯定是输出 N O NO NO,若小于了区间个数则将相邻的区间合并一下,最后变成m个区间就行了。
- 但是这个题对于位置和数值都非常的绕,注意不要写飘了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 3e5+100;
int pos[maxn], n, m, num1[maxn], num2[maxn], father[maxn], Max[maxn], ans[maxn];
bool vis[maxn];
vector <int> ve[maxn];
void init() {
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) {
scanf("%d", &num1[i]);
pos[num1[i]] = i;
father[i] = i;
Max[i] = i;
}
for(int i=1;i<=n;i++) scanf("%d", &num2[i]);
}
int find(int x) {
if(x == father[x]) return x;
return father[x] = find(father[x]);
}
void merge(int x,int y) {
int fx = find(x);
int fy = find(y);
if(fx != fy) {
father[fy] = father[fx];
}
}
void merge_all() {
int tail;
for(int i=1;i<=n;i=tail+1) {
int fi = find(i);
tail = Max[fi];
for(int j=i+1;j<=tail;j++) {
int fx = find(j);
if(fx != fi) {
tail = max(tail, Max[fx]);
merge(i, j);
}
}
}
}
int main() {
// freopen("1.in.txt", "r", stdin);
init();
for(int i=1;i<=n;i++) {
//找到相同字符的区间,这里只需要记录一下区间右端点的值就行了
int pos1 = pos[num1[i]], pos2 = pos[num2[i]];
int Max1 = Max[find(pos1)], Max2 = Max[find(pos2)];
merge(pos1, pos2);
Max[find(pos1)] = max(Max1, Max2);
}
set <int> se;
merge_all();//将区间有交集的合并
for(int i=1;i<=n;i++) {//记录有多少个区间
int fi = find(i);
se.insert(fi);
ve[fi].push_back(num1[i]);
}
if(se.size() < m) {
puts("NO");
return 0;
}
puts("YES");
int Index = 'a'-1;//当m小于区间个数的时候要合并一些相邻的区间
for(int i=1;i<=n;i++) {
int fi = find(i);
if(!vis[fi]) {
if(Index < m+'a'-1)
Index++;
vis[fi] = true;
for(int j=0;j<ve[fi].size();j++) {
int va = ve[fi][j];
ans[va] = Index;
}
}
}
for(int i=1;i<=n;i++) printf("%c", ans[i]);
return 0;
}
G. Path Queries
题意:现在有一棵树,树上每个边有个权值,现在有 m m m次操作,每次操作分别是将边权大于等于 q q q的边删去,问还剩多少条路径。
解题心得:删边是不可能删边的,肯定是离线将询问全部记录下来,然后从小到大将边加进去,用并查集维护并且记录一下每个并查集内有多少个点,然后一个不停加入边合并累加值的过程。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 3e5+100;
struct Edge {
ll u, v, va;
bool operator < (const Edge &x) const {
return va < x.va;
}
}edge[maxn];//记录每一条边,用于并查集操作
ll n, m, father[maxn], cnt[maxn];
ll ans[maxn];
struct Query {//记录每一个询问用于离散化
ll pos, va;
bool operator < (const Query& x) const {
return va < x.va;
}
}q[maxn];
void init() {
scanf("%lld%lld",&n, &m);
for(ll i=0;i<=n+1;i++) {
father[i] = i;
cnt[i] = 1;
}
for(ll i=1;i<n;i++) {
ll a, b, c;scanf("%lld%lld%lld", &a, &b, &c);
edge[i] = {a, b, c};
}
sort(edge+1, edge+n);
for(ll i=1;i<=m;i++) {
scanf("%lld",&q[i].va);
q[i].pos = i;
}
sort(q+1, q+1+m);
}
ll find(ll x) {
if(x == father[x]) return x;
return father[x] = find(father[x]);
}
void merge(ll x, ll y) {
ll fx = find(x);
ll fy = find(y);
cnt[father[fy]] += cnt[father[fx]];
father[fx] = father[fy];
}
int main() {
// freopen("1.in.txt", "r", stdin);
init();
ll Ans = 0;//记录累加值
ll Index = 1;
for(ll i=1;i<=m;i++) {//将每个询问和上一个询问差值中间的边加入
while(Index <= (n-1) && edge[Index].va <= q[i].va) {
Edge now = edge[Index];
ll fu = find(now.u), fv = find(now.v);
if(fu != fv) {
Ans += cnt[fu] * cnt[fv];
merge(now.u, now.v);
}
Index++;
}
ans[q[i].pos] = Ans;
}
for(ll i=1;i<=m;i++) printf("%lld ", ans[i]);
return 0;
}