https://codeforces.com/contest/1592
A
给你若干个武器的攻击力,不能用相同的武器攻击敌人,问最少需要攻击多少次才能消灭敌人
- 显然先用伤害最大的武器攻击,然后用第二大的武器,一直这样进行下去即可,先把两个武器看做一个整体考虑
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while(t--){
int n, h;
cin >> n >> h;
vector<int> a(n);
for(int i=0;i<n;i++){
cin >> a[i];
}
sort(a.begin(), a.end());
int k = h / (a[n - 1] + a[n - 2]);
h %= (a[n - 1] + a[n - 2]);
if(h == 0) cout << k * 2 << '\n';
else{
h -= a[n - 1];
if(h <= 0) cout << k * 2 + 1 << '\n';
else cout << k * 2 + 2 << '\n';
}
}
return 0;
}
B
- 如果某个位置错了,那他必须能够移动到最左面或者最右面,否则就无法调整,一旦能移动到最左面或者最右面,就有可能得到调整,在后续的判断中进一步验证中间的位置能否得到调整
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t, n, x;
cin >> t;
while(t--){
cin >> n >> x;
vector<int> a(n);
vector<int> b(n);
for(int i=0;i<n;i++){
cin >> a[i];
b[i] = a[i];
}sort(a.begin(), a.end());
bool f = true;
for(int i=0;i<n;i++){
if(a[i] != b[i] && x > i && x > (n - 1 - i)){
f = false;
}
}
cout << (f ? "YES" : "NO") << '\n';
}
return 0;
}
C
给你一颗树,给定每个节点的点权,问能否通过删除某些边使得产生的若干子树内部异或和相等,删除的边的数量在 1 1 1和 k − 1 k-1 k−1之间(闭区间)
- 显然如果初始异或和就为 0 0 0,那么根据异或的性质,删除任意一条边均可达到目的;如果初始异或和不是 0 0 0,假设为 p p p,如果我们可以分出 n u m num num棵子树,那么我们必然可以分出 n u m − 2 num-2 num−2棵子树,因为 x ⊕ x ⊕ x = x x\oplus x\oplus x=x x⊕x⊕x=x,所以我们找子树异或和为 p p p的子树看有多少棵,如果数量 ≥ \geq ≥ 2 2 2,那么就一定有 ≥ 3 \geq3 ≥3棵树的内部异或和 = p =p =p,分成三棵树即可
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 100;
struct Edge{
int next;
int to;
int val;
}edge[MAXN];
int head[MAXN];
int a[MAXN];
int cnt;
int p, f;
void Add_Edge(int u, int v, int w){
edge[cnt].next = head[u];
edge[cnt].to = v;
edge[cnt].val = w;
head[u] = cnt++;
}
int dfs(int u, int fa){
int ans = a[u];
for(int i=head[u];~i;i=edge[i].next){
int v = edge[i].to;
if(v == fa) continue;
int t = dfs(v, u);
if(t == p){
f += 1;
}else{
ans ^= t;
}
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t, n, k, u, v;
cin >> t;
while(t--){
cin >> n >> k;
p = f = cnt = 0;
for(int i=1;i<=n;i++){
cin >> a[i];
p ^= a[i];
}
memset(head, -1, sizeof head);
for(int i=1;i<n;i++){
cin >> u >> v;
Add_Edge(u, v, 1);
Add_Edge(v, u, 1);
}
if(p == 0){
cout << "YES\n";
continue;
}else{
if(k >= 3){
dfs(1, -1);
if(f >= 2){
cout << "YES\n";
continue;
}
}
}
cout << "NO\n";
}
return 0;
}
D
给你一棵树,现在要进行十二次以内的询问,每次询问任意两个节点之间的最大边权,求出树上最大边权
- 看到 n ≤ 2000 n\leq 2000 n≤2000和 12 12 12,能够想到应该是二分,问题是现在如果树上二分,不好办,考虑如何将树上问题转化为一条链呢?考虑 d f s dfs dfs序或者 b f s bfs bfs序,我使用 b f s bfs bfs序,选择孩子节点最多的那个点作为树根,这可以使用逆拓扑序确定最后的序列,然后在这个序列上进行二分,每次都询问经过根节点的边权最大值,不出现在这一侧就出现在那一侧,不断二分直到锁定节点,最终的答案就是这个节点和它的父亲节点之间的边权
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n, u, v;
cin >> n;
vector<vector<int>> g(n);
for(int i=1;i<n;i++){
cin >> u >> v;
u -= 1;
v -= 1;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> d(n);
queue<int> q;
for(int i=0;i<n;i++){
d[i] = g[i].size();
if(d[i] == 1) q.push(i);
}
vector<int> t;
while(!q.empty()){
u = q.front();
q.pop();
t.push_back(u);
for(auto i : g[u]){
d[i] -= 1;
if(d[i] == 1){
q.push(i);
}
}
}
reverse(t.begin(), t.end());
v = t[0];
q.push(v);
vector<int> vs(n, -1);
while(!q.empty()){
u = q.front();
q.pop();
for(auto i : g[u]){
if(vs[i] != u){
vs[i] = u;
q.push(i);
}
}
}
cout << "? " << n;
for(int i=1;i<=n;i++){
cout << ' ' << i;
}cout << endl;
int mx;
cin >> mx;
int l = 1;
int r = n;
while(r - l > 1){
int mid = ((r - l) >> 1) + l;
cout << "? " << mid;
for(int i=0;i<mid;i++){
cout << ' ' << t[i] + 1;
}cout << endl;
int val;
cin >> val;
if(mx == val){
r = mid;
}else{
l = mid;
}
}
int a = t[r - 1];
int b = vs[a];
cout << "! " << a + 1 << ' ' << b + 1;
return 0;
}
E
给定一个序列,问其中 [ l , r ] [l,r] [l,r]区间 & > ⊕ \&\gt \oplus &>⊕的最长区间有多长,如果不存在就输出 0 0 0
- 首先考虑什么时候 & > ⊕ \&\gt \oplus &>⊕,因为与比较特殊,必须都是 1 1 1才能是 1 1 1,否则就是 0 0 0,所以对于一个奇数区间段,无论是哪种情况异或结果都是 1 1 1,也就是说一个区间长度为奇数的区间是不可能成立的
- 如果是一个偶数长度的区间,我们来进行这样的考虑,枚举每一个二进制位,对 a a a数组计算这个二进制位之前的数字的前缀异或和 p r e [ i ] pre[i] pre[i],如果当前二进制位可能合法,那么就必须在之前找到一个 p r e [ i ] pre[i] pre[i]使得和当前相等因为如果有 p r e [ l − 1 ] = p r e [ r ] pre[l-1]= pre[r] pre[l−1]=pre[r],那么就会有 [ l , r ] [l,r] [l,r]这个区间内异或和为 0 0 0,
- 现在我们使用两个指针, l , r l,r l,r分别表示往左走第一个当前二进制位是 0 0 0的下标,当前位置下标,也就是说这两个位置的当前二进制位一个是 0 0 0,另一个是 1 1 1,更新答案的时候只需要将 a n s ans ans和 r − l r-l r−l取最大即可
- 那么具体该如何操作呢?我们开个桶,像这样
gp_hash_table<int, vector<int>> vs[2];
一个哈希表,表示奇数位或者偶数位前缀异或和是多少的一个集合,首先 f o r for for一圈把哈希表填好,之后我们在哈希表里面二分查找有没有当前合法的 l l l,如果有,就更新答案,如果 r r r位置上的二进制值是 0 0 0,那么就更新 l l l为当前的 r r r
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n + 1);
for(int i=1;i<=n;i++){
cin >> a[i];
}
int ans = 0;
for(int k=0;k<=20;k++){
vector<int> pre(n + 1);
gp_hash_table<int, vector<int>> vs[2];
vs[0][0].push_back(0);
for(int i=1;i<=n;i++){
pre[i] = (pre[i - 1] ^ (a[i] >> k));
vs[i & 1][pre[i]].push_back(i);
}
int l = 0;
for(int r=1;r<=n;r++){
if((a[r] >> k) & 1){
auto &v = vs[r & 1][pre[r]];
auto it = lower_bound(v.begin(), v.end(), l);
if(it != v.end()) ans = max(ans, r - *it);
}else{
l = r;
}
}
}
cout << ans;
return 0;
}