A. Round Down the Price
题目大意:给一个数字 n n n, 1 ≤ n ≤ 1 0 9 1\le n \le 10^9 1≤n≤109,问将 n n n最少减少多少使得 n = 1 0 x , x ∈ N + n=10^x, \ x \in \N_+ n=10x, x∈N+
解题思路:最后变成的样子一定是 x 0 ⋯ 0 ( x ∈ [ 1 , 9 ] ) x 0 \cdots 0 (x \in [1, 9]) x0⋯0(x∈[1,9])在写的时候注意, n n n的第 1 1 1位要少减 1 1 1个让它保持是 1 1 1。时间复杂度 O ( t × l g n ) O(t \times lgn) O(t×lgn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve() {
int n; cin >> n;
vector<int>v;
while (n) {
v.push_back(n % 10); n /= 10;
}
reverse(v.begin(), v.end());
int s = 0;
for (int i = 0; i < v.size(); i ++ ) {
if (i == 0) s = v[i] - 1;
if (i >= 1) s = s * 10 + v[i];
}
cout << s << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
B. Polycarp Writes a String from Memory
题目大意:给定一个字符串 s s s,Ploycarp每次只能读3个不同的字符,问读完字符串最少要读几次
解题思路:用map<char, int>
记录当前Ploycarp已读每个字符的字符数,cnt
记录Ploycarp,一旦cnt
超过
3
3
3,就重新读,清空map
,并且让
c
n
t
=
1
cnt=1
cnt=1,因为已经在读下一组的第一个字符啦。时间复杂度
O
(
t
×
n
)
O(t \times n)
O(t×n)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve() {
string s; cin >> s;
map<char, int>mp;
int ans = 1, cnt = 0;
for (int i = 0; i < s.size(); i ++ ) {
mp[s[i]] ++ ;
if (mp[s[i]] == 1) cnt ++ ;
if (cnt == 4) {
mp.clear(); mp[s[i]] ++ ; cnt = 1;
ans ++ ;
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
C. Train and Queries
题目大意::给定一个长度为 n n n的序列 a a a, 1 ≤ n ≤ 2 × 1 0 5 1 \le n \le 2 \times 10^5 1≤n≤2×105, q q q次询问, 1 ≤ q ≤ 2 × 1 0 5 1 \le q \le 2\times10^5 1≤q≤2×105, 每次询问 a l , a r a_l, a_r al,ar,问序列中是否存在 a l a_l al位置在 a r a_r ar的左边
解题思路:从前往后维护每个数
a
i
a_i
ai第一次出现的位置
p
r
[
a
[
i
]
]
pr\bigl[a[i]\bigr]
pr[a[i]] 和 从后往前维护每个数
a
i
a_i
ai最后一次出现的位置
n
x
[
a
[
i
]
]
nx\bigl[a[i]\bigr]
nx[a[i]],每次询问时如果满足
p
r
[
a
l
]
≤
n
x
[
a
r
]
pr[a_l]\le nx[a_r]
pr[al]≤nx[ar]就YES
,否则NO
。时间复杂度
O
(
t
×
(
n
+
q
)
)
O\bigl(t \times (n+q)\bigr)
O(t×(n+q))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
int a[N];
void solve() {
int n, q;cin >> n >> q;
map<int, int>pr, nx;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) {
if (!pr[a[i]]) pr[a[i]] = i;
}
for (int i = n; i >= 1; i -- ) {
if (!nx[a[i]]) nx[a[i]] = i;
}
while (q -- ) {
int a, b; cin >> a >> b;
if (pr[a] && nx[b] && pr[a] <= nx[b]) cout << "YES\n";
else cout << "NO\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
D. Not a Cheap String
题目大意:给定一个字符串 s s s,长度 1 ≤ s . l e n g t h ( ) ≤ 2 × 1 0 5 1\le s.length()\le 2\times10^5 1≤s.length()≤2×105,给定一个数字 p p p, 1 ≤ p ≤ 5200000 1\le p \le 5200000 1≤p≤5200000,字符串中 a a a对应 1 1 1, b b b对应 2 2 2 … z z z对应 26 26 26,字符串的和定义为把这些数字加起来。可以任意删字符串 s s s任意位置,输出删完的最长字符串的和 ≤ \le ≤ p p p的字符串
解题思路:因为可以任意位置删且最后长度最长,肯定先删大的,用cnt[27]
数组记录每个字符出现的数量,
f
o
r
i
i
n
[
26
→
1
]
for \ i \ in \ [26\rightarrow1]
for i in [26→1],如果字符串的和大于
p
p
p且
c
n
t
[
i
]
>
0
cnt[i]>0
cnt[i]>0,就删它。最后按照原先字符串的顺序根据
c
n
t
cnt
cnt的值输出。时间复杂度
O
(
t
×
m
i
n
(
p
,
26
×
n
)
)
O\bigl(t\times min(p,26\times n)\bigr)
O(t×min(p,26×n))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
void solve() {
string s; cin >> s;int n = s.size();
int p; cin >> p;
s = " " + s;
int sum = 0;
int cnt[28];
for (int i = 0; i <= 27; i ++ ) cnt[i] = 0;
for (int i = 1; i <= n; i ++ ) sum += s[i] - 'a' + 1, cnt[s[i] - 'a' + 1] ++ ;
for (int i = 26; i >= 1; i -- ) {
while (sum > p && cnt[i]) cnt[i] -- , sum -= i;
}
string ans = "";
for (int i = 1; i <= n; i ++ ) {
if (cnt[s[i] - 'a' + 1]) ans += s[i], cnt[s[i] - 'a' + 1] -- ;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
E. Split Into Two Sets
题目大意:给定
n
n
n个多米诺骨牌,每个多米诺骨牌上有两个数字,你将这些多米诺骨牌平均分成两组,若满足每组中不出现重复数字输出YES
,否则输出NO
解题思路:种类并查集,思想:敌人的敌人就是朋友。扩大并查集的规模到 [ 1 , 2 n ] [1,2n] [1,2n],对于 ∀ i ∈ [ 1 , n ] \forall i\in[1,n] ∀i∈[1,n],他的敌人是 i + n i+n i+n,这个敌人是假想出来的。假设 1 1 1和 2 2 2是敌人, 1 1 1和 2 + n 2+n 2+n是一类, 2 2 2和 1 + n 1+n 1+n是一类;假设 1 1 1和 3 3 3是敌人, 1 1 1和 3 + n 3+n 3+n是一类, 3 3 3和 1 + n 1+n 1+n是一类。则 2 2 2和 3 3 3是同一类,即敌人的敌人是朋友。
对于这道题,我们对同一个多米诺骨牌的两个数字和组号连边,假设 1 1 1包含的组号是 2 2 2和 4 4 4,用并查集判断组号 2 2 2和组号 4 4 4在同一个集合中就不行,这样会导致两个 1 1 1在同一个集合中。时间复杂度 O ( t × n l o g n ) O(t\times nlogn) O(t×nlogn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4e5 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-8;
typedef pair<int, int> PII;
int p[N];
vector<int>g[N];
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
void merge(int a, int b) {
a = find(a), b = find(b);
if (a != b) p[a] = b;
}
void solve() {
int n; cin >> n;
for (int i = 1; i <= 2 * n; i ++ ) p[i] = i, g[i].clear();
for (int i = 1; i <= n; i ++ ) {
int a, b; cin >> a >> b;
g[a].push_back(i), g[b].push_back(i);
}
bool ok = true;
for (int i = 1; i <= n; i ++ ) {
if (g[i].size() != 2) {
ok = false;
break;
}
if (find(g[i][0]) == find(g[i][1])) {
ok = false;
break;
}
merge(g[i][0], g[i][1] + n), merge(g[i][1], g[i][0] + n);
}
if (ok) cout << "YES\n";
else cout <<"NO\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
F. Equate Multisets
题目大意:给定两个集合 A \text A A和集合 B \text B B,集合 A \text A A中的元素不能动,集合 B \text B B中的元素可以乘 2 2 2或除 2 2 2(向下取整)。问是否集合 B \text B B能等于集合 A \text A A
解题思路:因为能无限次乘除2,我们先把集合 A \text A A和集合 B \text B B中所有 2 2 2的因子消掉,使得所有的元素都是奇数。现在对集合 B \text B B中的元素进行除 2 2 2每次得到的值去二分在集合 A \text A A中是否找到,找到就把集合 B \text B B中的这个数删除;为什么不去把集合 A \text A A中的元素乘 2 2 2和集合 B \text B B中比较,因为这样很麻烦,是否要+1也不知道。
注意:二分的时候要特判是否越界
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
int a[N], b[N];
void solve() {
int n; cin >> n;
vector<int>vec1, vec2;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
while (a[i] % 2 == 0 && a[i] > 0) a[i] >>= 1;
vec1.push_back(a[i]);
}
for (int i = 1; i <= n; i ++ ) {
cin >> b[i];
while (b[i] % 2 == 0 && b[i] > 0) b[i] >>= 1;
vec2.push_back(b[i]);
}
sort(vec1.begin(), vec1.end()); sort(vec2.begin(), vec2.end());
bool ok = 1;
for (int i = 0; i < vec2.size(); i ++ ) {
int x = vec2[i];
while (x > 0) {
int pos = lower_bound(vec1.begin(), vec1.end(), x) - vec1.begin();
if (pos < vec1.size() && x == vec1[pos]) {
vec1.erase(vec1.begin() + pos);
break;
}
x >>= 1;
}
}
if (vec1.size() == 0) cout << "YES\n";
else cout << "NO\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
cin >> _;
//_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
ps:vp的时候出F了没出E😭
G1. Passable Paths (easy version)
题目大意:给定一棵树,有
q
q
q个询问,
1
≤
q
≤
5
1\le q\le 5
1≤q≤5,每次询问一个序列
p
1
,
p
2
,
⋯
,
p
k
p_1,p_2,\cdots,p_k
p1,p2,⋯,pk,
1
≤
k
≤
2
×
1
0
5
1\le k \le2\times10^5
1≤k≤2×105,若这个序列在树中走完不会重复经过一条边输出YES
,否则输出NO
解题思路:因为 q q q足够小,我们可以每次询问暴力的走完一整棵树,时间复杂度是 O ( q n ) O(qn) O(qn),很常规的 d f s dfs dfs预处理出来每个节点的深度,从最深的节点开始走,如果这个序列在树中每条边只能走一次,它只有一种方向能走。若是较高的节点开始走,它可能需要往多个儿子方向走,不好处理。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
vector<int>g[N];
int dep[N], b[N];
bool vis[N];
void dfs1(int u, int fa, int depth) {
dep[u] = depth;
for (int i : g[u]) {
if (i == fa) continue;
dfs1(i, u, depth + 1);
}
}
int dfs2(int u) {
int y = 0;
vis[u] = 1;
for (int i : g[u]) {
if (!vis[i]) {
y = dfs2(i);
if (y) break;//这一条路下去有返回值了,不再走u的其他路
}
}
return y + b[u];
}
void solve() {
int n; cin >> n;
for (int i = 1; i <= n; i ++ ) g[i].clear();
for (int i = 1; i < n; i ++ ) {
int a, b; cin >> a >> b;
g[a].push_back(b), g[b].push_back(a);
}
dfs1(1, 0, 1);
int q; cin >> q;
while (q -- ) {
int k; cin >> k;
int x;
for (int i = 1; i <= n; i ++ ) vis[i] = b[i] = 0;
for (int i = 1; i <= k; i ++ ) {
int p; cin >> p;
if (i == 1 || dep[p] > dep[x]) {
x = p;
}
b[p] = 1;
}
if (dfs2(x) == k) cout << "YES\n";
else cout << "NO\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
//cin >> _;
_ = 1;
while (_ -- ) {
solve();
}
return 0;
}
G2. Passable Paths (hard version)
和G1的唯一区别是 1 ≤ q ≤ 1 0 5 1 \le q \le 10^5 1≤q≤105
解题思路:类似树上直径的思想,树上任意一点的最远点一定是树上直径的一个端点,而从这个端点出发再走到最远点是树上直径的另一个端点。这题中查询的 k k k个点代替常规树上直径的 n n n个点,即只考虑这 k k k个点。这时从 k k k个点中的任意点出发到达这两个端点的距离之和一定是相同且最远的,若存在距离更远的,那么一定是走了重边。
树上两点 ( i , j ) (i,j) (i,j)间的距离 d i s t [ i ] + d i s t [ j ] − 2 ∗ d i s t [ l c a ( i , j ) ] dist[i]+dist[j]-2*dist\bigl[lca(i,j)\bigr] dist[i]+dist[j]−2∗dist[lca(i,j)]
时间复杂度 O ( l o g n ( n + q k ) ) O\bigl(logn(n+qk)\bigr) O(logn(n+qk))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10, M = 2 * N;
const int maxN = 2010;
const int mod = 1e9 + 7;
const int INF = (1ll << 31) - 1;
const ll LNF = 1e18;
const double eps = 1e-6;
typedef pair<int, int> PII;
vector<int>g[N];
int dep[N], b[N], fa[N][21];
bool vis[N];
void dfs1(int u, int father, int depth) {
dep[u] = depth; fa[u][0] = father;
for (int i = 1; i <= 20; i ++ ) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for (int i : g[u]) {
if (i == father) continue;
dfs1(i, u, depth + 1);
}
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int k = 20; k >= 0; k -- ) {
if (dep[fa[a][k]] >= dep[b]) a = fa[a][k];
}
if (a == b) return a;
for (int k = 20; k >= 0; k -- ) {
if (fa[a][k] != fa[b][k]) {
a = fa[a][k];
b = fa[b][k];
}
}
return fa[a][0];
}
int dist(int a, int b) {
return dep[a] + dep[b] - 2 * dep[lca(a, b)];
}
void solve() {
dep[0] = 0;
int n; cin >> n;
for (int i = 1; i <= n; i ++ ) g[i].clear();
for (int i = 1; i < n; i ++ ) {
int a, b; cin >> a >> b;
g[a].push_back(b), g[b].push_back(a);
}
dfs1(1, 0, 1);
int q; cin >> q;
while (q -- ) {
int k; cin >> k;
for (int i = 1; i <= k; i ++ ) cin >> b[i];
int mx1 = 0, s = 0;
for (int i = 1; i <= k; i ++ ) {
int d1 = dist(b[i], b[1]);
if (d1 > mx1) mx1 = d1, s = i;
}
int mx2 = 0, t = s;
for (int i = 1; i <= k; i ++ ) {
int d2 = dist(b[i], b[s]);
if (d2 > mx2) mx2 = d2, t = i;
}
bool ok = 1;
for (int i = 1; i <= k; i ++ ) {
if (dist(b[i], b[s]) + dist(b[i], b[t]) != mx2) {
ok = false;
break;
}
}
cout << (ok == true ? "YES" : "NO") << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _;
//cin >> _;
_ = 1;
while (_ -- ) {
solve();
}
return 0;
}