C - Bakry and Partitioning(贪心)
假设所有点的异或和为(x),然后被拆成了(m)个联通块,由于各个联通块的异或值都相同,有
[x_1 oplus x_2 oplus ... oplus x_m = x
]
可得每个联通块的值必须都相等。所以如果(x=1),答案为YES(删除任意一条边即可);否则直接贪心dfs自底向上找是否有子树异或值为(x)的,找到就删掉。只要个数大于等于2,答案就是YES。
#include <bits/stdc++.h>
#define endl '
'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e5 + 10;
const double eps = 1e-5;
int arr[N];
int val[N];
int cnt;
vector<int> np[N];
void dfs(int p, int fa, int x) {
val[p] = arr[p];
for(int nt : np[p]) {
if(nt == fa) continue;
dfs(nt, p, x);
val[p] ^= val[nt];
}
if(val[p] == x) {
cnt++;
val[p] = 0;
}
}
int main() {
IOS;
int t;
cin >> t;
while(t--) {
int n, k;
cin >> n >> k;
k--;
int x = 0;
for(int i = 1; i <= n; i++) {
np[i].clear();
cin >> arr[i];
x ^= arr[i];
}
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
}
cnt = 0;
dfs(1, 0, x);
if(x == 0) cout << "YES" << endl;
else {
if(k <= 1) cout << "NO" << endl;
else {
if(cnt >= 2) cout << "YES" << endl;
else cout << "NO" << endl;
}
}
}
}
D - Hemose in ICPC (欧拉序)
每个询问本质就是求最大边权。询问数这么少,显然是二分。但是不能直接二分点集,必须保证点集是连通的并且划分尽量均匀。
原做法就是找重心,然后将点尽量等分成两个联通块,二分。写起来又臭又长。
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
const int N = 3e5 + 10;
const double eps = 1e-5;
vector<int> np[N];
int cnt[N];
int cntq;
void dfs(int p, int fa) {
cnt[p] = 1;
for(int nt : np[p]) {
if(nt == fa) continue;
dfs(nt, p);
cnt[p] += cnt[nt];
}
}
int mid, tar;
vector<int> pre, las;
void getask(int p, int fa, int tot) {
vector<PII> num;
if(fa) num.push_back({tot - cnt[p], fa});
for(int nt : np[p]) {
if(nt == fa) continue;
num.push_back({cnt[nt], nt});
}
if(num.size() > 1) {
sort(num.begin(), num.end());
int sum = 0, mip = 0, dd = INF;
for(int i = 0; i < num.size() - 1; i++) {
sum += num[i].first;
int d = abs(tot - 1 - 2 * sum);
if(d < dd) {
dd = d;
mip = i;
}
}
if(dd < mid) {
mid = dd;
tar = p;
pre.clear();
las.clear();
for(int i = 0; i <= mip; i++) pre.push_back(num[i].second);
for(int i = mip + 1; i < num.size(); i++) las.push_back(num[i].second);
}
}
for(int nt : np[p]) {
if(nt == fa) continue;
getask(nt, p, tot);
}
}
vector<int> asknum;
int ask() {
cntq++;
assert(cntq <= 12);
cout << "? " << asknum.size();
for(int v : asknum) cout << " " << v;
cout << endl;
asknum.clear();
int res;
cin >> res;
return res;
}
void pushtree(int p, int fa) {
for(int nt : np[p]) {
if(nt == fa) continue;
asknum.push_back(nt);
pushtree(nt, p);
}
}
PII solve(int n, int mx) {
int rt = 1;
while(1) {
dfs(rt, 0);
if(cnt[rt] <= 2) break;
mid = INF;
getask(rt, 0, cnt[rt]);
asknum.push_back(tar);
for(int p : pre) {
asknum.push_back(p);
pushtree(p, tar);
}
if(ask() == mx) np[tar] = pre;
else np[tar] = las;
rt = tar;
}
return {rt, np[rt].front()};
}
int main() {
int n;
cin >> n ;
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
}
for(int i = 1; i <= n; i++) asknum.push_back(i);
int mx = ask();
PII ans = solve(n, mx);
cout << "! " << ans.first << " " << ans.second << endl;
}
正解是欧拉序。所谓欧拉序就是沿着树外围走一圈经过再回到原点的结点序列,它和dfs序非常相似。例如样例1的欧拉序为:1 2 3 2 4 2 1 5 6 5 1
。欧拉序中相邻两个点可以看作一条边,你会发现,在欧拉序中,任意一个区间内的点,它们都是连通的(因为是连续经过的)!因此直接按照欧拉序二分点集即可。
最大询问次数(1 + log 2n = 2+log 1000 le 12)
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 3e5 + 10;
const double eps = 1e-5;
vector<int> np[N];
vector<int> pos;
void euler(int p, int fa) {
pos.push_back(p);
for(int nt : np[p]) {
if(nt == fa) continue;
euler(nt, p);
pos.push_back(p);
}
}
int main() {
int n;
cin >> n;
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
np[u].push_back(v);
np[v].push_back(u);
}
cout << "? " << n;
for(int i = 1; i <= n; i++) cout << " " << i;
cout << endl;
int mx;
cin >> mx;
euler(1, 0);
int len = pos.size() - 1;
int l = 0, r = len - 1;
while(l <= r) {
int mid = (l + r) / 2;
set<int> s;
for(int i = mid; i <= len; i++) {
s.insert(pos[i]);
}
cout << "? " << s.size();
for(auto p : s) cout << " " << p;
cout << endl;
int res;
cin >> res;
if(res == mx) {
l = mid + 1;
} else {
r = mid - 1;
}
}
cout << "! " << pos[r] << " " << pos[r + 1] << endl;
}
E - Bored Bakry(位,思维)
假设(x=a_l & a_{l+1} & ... & a_r),(y=a_l oplus a_{l+1}oplus ... oplus a_r)。如果区间长度为奇数,那么(y)的二进制位中至少包含(x),即(xle y),所以区间长度不能是奇数;如果区间长度为偶数,那么(y)的二进制位中,(x)为1的位置(y)必定为0,这意味着(x eq y)。如果要(xge y),那么(x)最高位1以上的位置中(y)不能为1。
故直接拆位,枚举(x)的最高位,找到当前位下连续的1的区间(这样的区间与起来这一位才能为1),统计区间中最高位以上异或为0的最长区间是多长,这个直接用异或前缀和(O(n))搞定。
总时间复杂度(O(nlog m)),(m)为值域。
小坑:
- 值域要稍微开大一点,因为是二进制,异或出来可能会比最大值稍大。
#include <bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 2e6 + 10;
const double eps = 1e-5;
int vxor[N];
int prexor[N];
int arr[25][N];
int pos[2][N];
int cal(int l, int r) {
vector<int> tar;
prexor[l - 1] = 0;
for(int i = l; i <= r; i++) {
prexor[i] = vxor[i];
prexor[i] ^= prexor[i - 1];
}
for(int i = l - 1; i < r; i++) {
if(pos[i % 2][prexor[i]] == -1) {
pos[i % 2][prexor[i]] = i;
tar.push_back(prexor[i]);
}
}
int res = 0;
for(int i = l; i <= r; i++) {
int p = pos[i % 2][prexor[i]];
if(p != -1) {
res = max(res, i - p);
}
}
for(int p : tar) pos[0][p] = pos[1][p] = -1;
return res;
}
int main() {
memset(pos, -1, sizeof pos);
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
int x;
cin >> x;
for(int j = 0; j <= 22; j++) {
arr[j][i] = (bool)(x & (1 << j));
}
}
int ans = 0;
for(int i = 22; i >= 0; i--) {
int p = 1;
while(1) {
while(p <= n && !arr[i][p]) p++;
if(p > n) break;
int l = p;
while(p <= n && arr[i][p]) p++;
int r = p - 1;
ans = max(ans, cal(l, r));
}
for(int j = 1; j <= n; j++) {
vxor[j] ^= (arr[i][j] << i);
}
}
cout << ans << endl;
}