Codeforces Round 1020 div3 个人题解(A~G1)
Dashboard - Codeforces Round 1020 (Div. 3) - Codeforces
A. Dr. TC
题目大意
给定一个01字符串 s s s ,由这个字符串生成一个字符串数组,字符串数组中第 i i i 个字符串即对 s s s 的第 i i i 个字符进行01翻转 后得到的字符串,问字符串数组中有多少个1。
解题思路
我们先统计出原始串中的0和1的数量 c n t 0 , c n t 1 cnt0,cnt1 cnt0,cnt1 ,答案即为 c n t 0 × ( c n t 1 + 1 ) + c n t 1 × ( c n t 1 − 1 ) cnt0\times(cnt1+1)+cnt1 \times(cnt1-1) cnt0×(cnt1+1)+cnt1×(cnt1−1) 。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
int n;
cin >> n;
string s;
cin >> s;
int cnt1 = 0, cnt0 = 0;
for (auto &c : s)
{
cnt1 += (c == '1');
cnt0 += (c == '0');
}
cout << cnt0 * (cnt1 + 1) + cnt1 * (cnt1 - 1) << "\n";
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
B. St. Chroma
题目大意
给你一个长度为 n n n 的排列 p p p,元素是 0 0 0 到 n − 1 n-1 n−1。定义第 i i i 个前缀的上色值为 M E X ( p 1 , … , p i ) \mathrm{MEX}(p_1,\dots,p_i) MEX(p1,…,pi)。现在希望选一个喜欢的颜色 x x x,构造一个排列,使得在所有前缀中涂成颜色 x x x 的格子数量最大化。
解题思路
要使第 i i i 个前缀的上色为 M E X = x \mathrm{MEX}=x MEX=x,这个前缀中必须恰好包含 0 , 1 , … , x − 1 0,1,\dots,x-1 0,1,…,x−1 ,否则小于 x x x 的某个数没出现,MEX 会更小,且不包含 x x x,否则 MEX 会大于 x x x 。
令 L L L 为 0 , 1 , … , x − 1 0,1,\dots,x-1 0,1,…,x−1 中最后一个出现的位置,令 P P P 为元素 x x x 在全排列中的位置,则对于所有 i i i 满足 L ≤ i < P L \le i < P L≤i<P 时,前缀 [ 1.. i ] [1..i] [1..i] 恰好包含 0 , … , x − 1 {0,\dots,x-1} 0,…,x−1 且不含 x x x,因而 M E X = x \mathrm{MEX}=x MEX=x。所以可获得的次数为 max ( 0 , P − L ) \max(0,P-L) max(0,P−L)。要最大化 P − L P-L P−L,就应当让所有 0 , … , x − 1 0,\dots,x-1 0,…,x−1 尽可能集中在最前面,让 x x x 放到 n n n 。这样 L = x L=x L=x, P = n P=n P=n,获得次数 n − x n-x n−x,已是可行的最大值。
当 x = 0 x=0 x=0 时,前缀要 MEX=0,必须不含 0,直到看到 0 前的所有前缀都算。最优做法是把 0 放在最后,得到次数 n − 1 n-1 n−1 。
当 x = n x=n x=n 时,MEX 想要 n n n,只有在前缀包含了所有 0 … n − 1 0\ldots n-1 0…n−1 时才行,即只有完整前缀长度 n n n 一次。此时任意排列都能达到最多一次。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
int n, x;
cin >> n >> x;
if (x == 0)
{
for (int i = 1; i < n; i++)
{
cout << i << " ";
}
cout << 0 << "\n";
return;
}
if (x == n)
{
for (int i = 0; i < n; i++)
{
cout << i << " \n"[i == n - 1];
}
return;
}
for (int i = 0; i < x; i++)
{
cout << i << " ";
}
for (int i = x + 1; i < n; i++)
{
cout << i << " ";
}
cout << x << "\n";
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
C. Cherry Bomb
题目大意
给定两个长度为 n n n 的整型数组 a a a 和 b b b,其中 a i a_i ai 和 b i b_i bi 的取值范围为 [ 0 , k ] [0, k] [0,k],且有些 b i b_i bi 丢失,用 − 1 -1 −1 表示。我们称 a a a 和 b b b 是互补的,当且仅当存在一个常数 x x x,使得对所有 1 ≤ i ≤ n 1\le i\le n 1≤i≤n 都有 a i + b i = x a_i + b_i = x ai+bi=x,现在要为所有丢失的 b i b_i bi 填上一个值,使得填完后数组 b b b 与 a a a 互补。问有多少种方案。
解题思路
遍历一遍 a , b a,b a,b ,如果 b i ≠ − 1 b_i \neq -1 bi=−1,则可以算出一个候选值 x = a i + b i x = a_i + b_i x=ai+bi 。若后续出现不同的 a j + b j ≠ x a_j + b_j\neq x aj+bj=x,则不可能互补,答案为 0,如果确定了唯一的 x x x,则对每个缺失位置需要填入 x − a i x - a_i x−ai ( 0 ≤ x − a i ≤ k 0\le x-a_i\le k 0≤x−ai≤k ),若都满足,则方案数为 1。
如果所有 b i = − 1 b_i=-1 bi=−1,那么 x x x 可以在一个区间内任意取值。对 b i b_i bi 来说, 0 ≤ x − a i ≤ k ⟹ a i ≤ x ≤ a i + k 0 \le x - a_i \le k \quad\Longrightarrow\quad a_i \le x \le a_i + k 0≤x−ai≤k⟹ai≤x≤ai+k 。答案即为$ \min{a_i} -\max{a_i}+ k+1$。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
int n;
ll k;
cin >> n >> k;
vl a(n);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
vl b(n);
for (int i = 0; i < n; i++)
{
cin >> b[i];
}
ll x = -1;
for (int i = 0; i < n; i++)
{
if (b[i] != -1)
{
ll cur = a[i] + b[i];
if (x == -1)
{
x = cur;
}
else if (x != cur)
{
cout << 0 << "\n";
return;
}
}
}
if (x != -1)
{
for (int i = 0; i < n; i++)
{
if (b[i] == -1)
{
ll need = x - a[i];
if (need < 0 || need > k)
{
cout << 0 << "\n";
return;
}
}
}
cout << "1\n";
return;
}
ll mx = 0, mn = infll;
for (int i = 0; i < n; i++)
{
mx = max(mx, a[i]);
mn = min(mn, a[i] + k);
}
ll ans = 0;
if (mx <= mn)
ans = mn - mx + 1;
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
D. Flower Boy
题目大意
给两个序列长度为 n n n 和 m m m 的序列 a , b a,b a,b ,要求在 a a a 中恰好选出一个长度为 m m m 的子序列 c c c,使得 c i ≥ b i , i = 1 , 2 , … , m c_i \ge b_i,\quad i=1,2,\dots,m ci≥bi,i=1,2,…,m
在选之前,允许你在 a a a 的任意位置插入恰好一个值为 k k k 的元素,问最小的 k k k 应取何值,使得上述选取可行;若原序列已可满足,输出 0 0 0;若无论 k k k 取何值都不可行,输出 − 1 -1 −1。
解题思路
我们用
p
r
e
[
i
]
pre[i]
pre[i] 表示在不插花的情况下,只看原序列前
i
i
i 个花,最多能匹配需求数组
b
b
b 的前多少个:
pre
[
0
]
=
0
,
pre
[
i
]
=
{
pre
[
i
−
1
]
+
1
,
若 pre
[
i
−
1
]
<
m
且
a
i
≥
b
pre
[
i
−
1
]
+
1
,
pre
[
i
−
1
]
,
否则.
\text{pre}[0]=0,\quad \text{pre}[i]= \begin{cases} \text{pre}[i-1]+1, &\text{若 }\text{pre}[i-1]<m\text{ 且 }a_i\ge b_{\text{pre}[i-1]+1},\\ \text{pre}[i-1], &\text{否则.} \end{cases}
pre[0]=0,pre[i]={pre[i−1]+1,pre[i−1],若 pre[i−1]<m 且 ai≥bpre[i−1]+1,否则.
我们用
s
u
f
[
i
]
suf[i]
suf[i] 表示只看原序列从第
i
i
i 个到末尾,最多能从
b
b
b 的末尾匹配多少个:
suf
[
n
+
1
]
=
0
,
suf
[
i
]
=
{
suf
[
i
+
1
]
+
1
,
若 suf
[
i
+
1
]
<
m
且
a
i
≥
b
m
−
suf
[
i
+
1
]
,
suf
[
i
+
1
]
,
否则.
\text{suf}[n+1]=0,\quad \text{suf}[i]= \begin{cases} \text{suf}[i+1]+1, &\text{若 }\text{suf}[i+1]<m\text{ 且 }a_i\ge b_{m-\text{suf}[i+1]},\\ \text{suf}[i+1], &\text{否则.} \end{cases}
suf[n+1]=0,suf[i]={suf[i+1]+1,suf[i+1],若 suf[i+1]<m 且 ai≥bm−suf[i+1],否则.
这样我们就能快速知道,在插入点右侧还能匹配需求的多少尾部。
枚举所有插入点,计算最小
k
k
k
插在第
i
i
i 和第
i
+
1
i+1
i+1 个花之间,前半段最多已匹配
p
=
pre
[
i
]
p=\text{pre}[i]
p=pre[i] 个需求;若要让插入的那朵花去匹配需求的第
p
+
1
p+1
p+1 项,必须满足
k
≥
b
p
+
1
k\ge b_{p+1}
k≥bp+1 ,并且右侧还能匹配剩余
m
−
(
p
+
1
)
m-(p+1)
m−(p+1) ,即
suf
[
i
+
1
]
≥
m
−
p
−
1
\text{suf}[i+1]\ge m-p-1
suf[i+1]≥m−p−1 。
遍历所有 i ∈ [ 0 , n ] i\in[0,n] i∈[0,n],取能满足的最小 b p + 1 b_{p+1} bp+1 即为答案。如果前缀本身就能匹配 m m m 个,则输出 0,遍历后没有可行解则输出 − 1 -1 −1。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
int n, m;
cin >> n >> m;
vl a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
vl b(m + 1);
for (int i = 1; i <= m; i++)
{
cin >> b[i];
}
vl pre(n + 1);
for (int i = 1; i <= n; i++)
{
if (pre[i - 1] < m && a[i] >= b[pre[i - 1] + 1])
pre[i] = pre[i - 1] + 1;
else
pre[i] = pre[i - 1];
}
if (pre[n] >= m)
{
cout << 0 << "\n";
return;
}
vl suf(n + 2);
for (int i = n; i >= 1; i--)
{
if (suf[i + 1] < m && a[i] >= b[m - suf[i + 1]])
suf[i] = suf[i + 1] + 1;
else
suf[i] = suf[i + 1];
}
ll ans = infll;
for (int i = 0; i <= n; i++)
{
if (pre[i] >= m)
continue;
if (suf[i + 1] >= m - pre[i] - 1)
ans = min(ans, b[pre[i] + 1]);
}
if (ans == infll)
ans = -1;
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
E. Wolf
题目大意
有一个长度为 n n n 的排列 p p p ,以及 q q q 次独立查询。每次查询给定区间 [ l , r ] [l,r] [l,r] 和目标值 x x x。在对这个区间上模拟二分查找时,如果当前中点值与 x x x 比较的结果和数组实际顺序不一致,就会失败。允许在查询前任意选取 d d d 个 不是 x x x 的下标,将它们对应的值重新任意重排,来保证查找成功。求每次查询的最小 d d d,若无法通过任何重排成功则输出 − 1 -1 −1。
解题思路
阅读理解题,理解题意后不难。
首先预处理出每个值在排列中的位置,对 i = 1 , 2 , … , n i=1,2,\dots ,n i=1,2,…,n 记录 p o s [ i ] pos[i] pos[i]。
对于每个查询 L , R , x L,R,x L,R,x,令 k = pos [ x ] k=\text{pos}[x] k=pos[x],若 k ∉ [ L , R ] k\notin[L,R] k∈/[L,R] 则直接输出 − 1 -1 −1,否则在区间 [ l = L , r = R ] [l=L,r=R] [l=L,r=R] 上模拟二分查找,维护4个值:
c n t 0 cnt0 cnt0 :访问到且无需替换的 p [ m i d ] < x p[mid]<x p[mid]<x 次数
c n t 1 cnt1 cnt1 :访问到且无需替换的 p [ m i d ] > x p[mid]>x p[mid]>x 次数
c n t L cntL cntL :必须将 p [ m i d ] p[mid] p[mid] 当作 > x x x 来替换的次数
c n t R cntR cntR :必须当作 < x x x 来替换的次数
令 l = L , R = r , m i d = l + r 2 l=L,R=r,mid=\frac{l+r}{2} l=L,R=r,mid=2l+r
- 当 m i d < k mid \lt k mid<k 且 p [ m i d ] < x p[mid]\lt x p[mid]<x 时,向右搜索,累加小于 x x x 的访问数 c n t 0 + 1 cnt0+1 cnt0+1 。
- 当 m i d < k mid<k mid<k 且 p [ m i d ] > x p[mid]>x p[mid]>x 时,本来会向右但 p [ m i d ] p[mid] p[mid] 又不小于 x x x,因此必须把它当成大于 x x x 来替换, c n t L + 1 , c n t 1 + 1 cntL+1,cnt1+1 cntL+1,cnt1+1 ,并令 l = m i d + 1 l=mid+1 l=mid+1 。
- 当 m i d > k mid>k mid>k 且 p [ m i d ] > x p[mid]>x p[mid]>x 时,向左搜索,累加大于 x x x 的访问数 c n t 1 + 1 cnt1+1 cnt1+1 。
- 当 m i d > k mid>k mid>k 且 p [ m i d ] < x p[mid]<x p[mid]<x 时,本来会向左但 p [ m i d ] p[mid] p[mid] 又不大于 x x x,因此必须把它当成小于 x x x 来替换, c n t R + 1 , c n t 0 + 1 cntR+1,cnt0+1 cntR+1,cnt0+1 ,并令 r = m i d − 1 r=mid-1 r=mid−1。
当 m i d = k mid=k mid=k 停止。此时 c n t L cntL cntL 是需要替换成大于 x x x 的次数, c n t R cntR cntR 是需要替换成小于 x x x 的次数, c n t 0 cnt0 cnt0 / c n t 1 cnt1 cnt1 分别是整个过程中访问到的小于/大于 x x x 的点数。可用的小于 x x x 的数目为 ( x − 1 ) − c n t 0 (x-1)-cnt0 (x−1)−cnt0,可用的大于 x x x 的数目为 ( n − x ) − c n t 1 (n-x)-cnt1 (n−x)−cnt1;若它们无法分别覆盖 max ( 0 , c n t R − c n t L ) \max(0,cntR-cntL) max(0,cntR−cntL) 或 max ( 0 , c n t L − c n t R ) \max(0,cntL-cntR) max(0,cntL−cntR) 这两种多余替换需求,就输出 − 1 -1 −1,否则最少替换次数为 2 max ( c n t L , c n t R ) 2\max(cntL,cntR) 2max(cntL,cntR)。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
int n, q;
cin >> n >> q;
vi p(n + 1);
vi pos(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> p[i];
pos[p[i]] = i;
}
while (q--)
{
int L, R, x;
cin >> L >> R >> x;
int k = pos[x];
if (k < L || k > R)
{
cout << "-1 ";
continue;
}
int l = L, r = R;
int cntL = 0, cntR = 0, cnt0 = 0, cnt1 = 0;
vector<bool> vis(n + 1);
while (l <= r)
{
int mid = (l + r) >> 1;
vis[mid] = true;
if (mid == k)
break;
if (mid < k)
{
if (p[mid] < x)
cnt0++;
else
cntL++, cnt1++;
l = mid + 1;
}
else
{
if (p[mid] > x)
cnt1++;
else
cntR++, cnt0++;
r = mid - 1;
}
}
if (x - 1 - cnt0 < max(0, cntL - cntR) || n - x - cnt1 < max(0, cntR - cntL))
cout << "-1 ";
else
cout << 2 * max(cntL, cntR) << " ";
}
cout << '\n';
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
F. Goblin
题目大意
给定二值向量
s
=
(
s
1
,
s
2
,
…
,
s
n
)
∈
{
0
,
1
}
n
,
s=(s_1,s_2,\dots,s_n)\in\{0,1\}^n,
s=(s1,s2,…,sn)∈{0,1}n,
构造二值矩阵
G
∈
{
0
,
1
}
n
×
n
G\in\{0,1\}^{n\times n}
G∈{0,1}n×n:
G
i
,
j
=
{
1
−
s
i
,
i
=
j
,
s
j
,
i
≠
j
.
G_{i,j}=\begin{cases} 1 - s_i,& i=j,\\ s_j,& i\neq j. \end{cases}
Gi,j={1−si,sj,i=j,i=j.
视矩阵中值为 0 的单元格以四连通(上下左右)方式连通,求这些连通分量中的最大的联通分量大小。
解题思路
对于每个下标 j j j 满足 s j = 0 s_j=0 sj=0,将该列分为三部分:
- 上半段:所有 G i , j G_{i,j} Gi,j 使 1 ≤ i < j 1\le i\lt j 1≤i<j;共 j − 1 j-1 j−1 个格子,记作节点 U j U_j Uj ,联通块大小为 j − 1 j-1 j−1 。
- 下半段:所有 G i , j G_{i,j} Gi,j 使 $ j$;共 n − j n-j n−j 个格子,记作节点 D j D_j Dj ,联通块大小为 n − j n-j n−j 。
对于每个下标 j j j 满足 s j = 1 s_j=1 sj=1 , G j , j G_{j,j} Gj,j 为零,记作节点 T j T_j Tj,联通块大小为 1。
对于每个 1 ≤ j < n 1\le j\lt n 1≤j<n,若 s j = s j + 1 = 0 s_j=s_{j+1}=0 sj=sj+1=0,则:
- 合并 U j U_j Uj 与 U j + 1 U_{j+1} Uj+1(它们在行方向上上下段相连)
- 合并 D j D_j Dj 与 D j + 1 D_{j+1} Dj+1 (同理)
对每个 j j j 使 s j = 1 s_j=1 sj=1:
- 若 j > 1 j>1 j>1 且 s j − 1 = 0 s_{j-1}=0 sj−1=0,合并 T j T_j Tj 与 D j − 1 D_{j-1} Dj−1 , G j , j G_{j,j} Gj,j 可向左下连通。
- 若 j < n j\lt n j<n 且 s j + 1 = 0 s_{j+1}=0 sj+1=0,合并 T j T_j Tj 与 U j + 1 U_{j+1} Uj+1 , G j , j G_{j,j} Gj,j 可向右上连通。
所有列遍历合并后,遍历所有联通分量,联通分量大小最大值即为答案。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
struct DSU
{
vector<int> f; // 父节点
vector<ll> siz; // 按秩(近似)维护子树大小,用于合并优化
DSU() {}
DSU(int n) { init(n); }
// 初始化并查集,n 为节点总数
void init(int n)
{
f.resize(n);
iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
}
// 路径压缩查找根
int find(int x)
{
while (x != f[x])
x = f[x] = f[f[x]];
return x;
}
// 合并两个集合,并把权重累加到新的根
bool merge(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return false;
// 保证 siz[x] >= siz[y]
if (siz[x] < siz[y])
swap(x, y);
f[y] = x;
siz[x] += siz[y];
return true;
}
ll size(int x)
{
return siz[find(x)];
}
};
void solve()
{
int n;
string s;
cin >> n >> s;
vi U(n, -1);
vi D(n, -1);
vi T(n, -1);
int idx = 0;
for (int j = 0; j < n; j++)
{
if (s[j] == '0')
{
U[j] = idx++;
D[j] = idx++;
}
else
{
T[j] = idx++;
}
}
DSU dsu(idx);
for (int i = 0; i < n; i++)
{
if (s[i] == '0')
{
dsu.siz[U[i]] = i;
dsu.siz[D[i]] = n - i - 1;
}
}
for (int i = 0; i + 1 < n; i++)
{
if (s[i] == '0' && s[i + 1] == '0')
{
dsu.merge(U[i], U[i + 1]);
dsu.merge(D[i], D[i + 1]);
}
}
for (int i = 0; i < n; i++)
{
if (s[i] == '1')
{
int x = T[i];
if (i - 1 >= 0 && s[i - 1] == '0')
dsu.merge(x, D[i - 1]);
if (i + 1 < n && s[i + 1] == '0')
dsu.merge(x, U[i + 1]);
}
}
ll ans = 0;
for (int i = 0; i < idx; i++)
{
if (dsu.find(i) == i)
ans = max(ans, dsu.size(i));
}
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
G1. Baudelaire (easy version)
题目大意
给定一棵特殊的树(简单版本条件)树,树中每个节点都与节点 1 相连,但节点 1 不一定是根,共有 n n n 个节点。每个节点有一个初始权值,只有两种可能: + 1 +1 +1 或 − 1 -1 −1。
树有一个隐藏的根节点 rt \text{rt} rt,定义 f ( u ) = ∑ x ∈ rt ⇝ u val [ x ] f(u)=\sum_{x\in\text{rt}\rightsquigarrow u} \text{val}[x] f(u)=∑x∈rt⇝uval[x] ,即为从根到节点 u u u 路径上所有节点权值之和。
你可以进行两种交互式操作,总次数不能超过 n + 200 n+200 n+200:
? 1 k a_1 a_2 … a_k
,会返回 f ( a 1 ) + f ( a 2 ) + … + f ( a k ) f(a_1)+f(a_2)+…+f(a_k) f(a1)+f(a2)+…+f(ak) 。? 2 u
,将节点 u u u 的权值从 + 1 +1 +1 ↔ − 1 -1 −1 互换,为永久修改。
在交互结束后,你要输出所有节点最终的权值,。
解题思路
询问? 1 1 1
得到
F
0
=
f
(
1
)
.
F_0=f(1).
F0=f(1).
接着对每个
v
=
2
,
3
,
…
,
n
v=2,3,\dots,n
v=2,3,…,n 分别询问 ? 1 1 v
得到
F
0
(
v
)
=
f
(
v
)
.
F_0(v)=f(v).
F0(v)=f(v).
由于树为星形,类似菊花图,如果 v ≠ r o o t v\neq\mathrm{root} v=root,必有 f ( v ) = f ( 1 ) + v a l ( v ) ⟹ v a l ( v ) = F 0 ( v ) − F 0 . f(v)=f(1)+\mathrm{val}(v) \Longrightarrow\mathrm{val}(v)=F_0(v)-F_0. f(v)=f(1)+val(v)⟹val(v)=F0(v)−F0. ,当 v = r t v=\mathrm{rt} v=rt 时上述等式不成立,需要后续操作加以区分。
发送? 2 1
,将节点 1 的权值取反,再次发送? 1 1 1
得到
F
1
=
f
′
(
1
)
.
F_1=f'(1).
F1=f′(1).
设翻转前 v a l ( 1 ) = x \mathrm{val}(1)=x val(1)=x,隐藏根的权值为 v a l ( r t ) \mathrm{val}(\mathrm{rt}) val(rt),则 F 0 = v a l ( r t ) + x , F 1 = v a l ( r t ) − x ⟹ x = F 0 − F 1 2 F_0=\mathrm{val}(\mathrm{rt})+x, F_1=\mathrm{val}(\mathrm{rt})-x \Longrightarrow x=\frac{F_0-F_1}{2} F0=val(rt)+x,F1=val(rt)−x⟹x=2F0−F1 ,此时节点 1 的最终权值为 − x -x −x。
如果 F 0 = x F_0 = x F0=x , 则隐藏根即为 1,此时直接令 v a l ( 1 ) = − x , v a l ( v ) = F 0 ( v ) − F 0 ( v ≥ 2 ) \mathrm{val}(1)=-x, \mathrm{val}(v)=F_0(v)-F_0(v\ge2) val(1)=−x,val(v)=F0(v)−F0(v≥2) 。
如果 F 0 ≠ x F_0 \neq x F0=x , 则隐藏根即不为 1,它在集合 2 , 3 , … , n {2,3,\dots,n} 2,3,…,n 中。
假设对叶子进行翻转,那么路径和应该为 p r e ( v ) = F 0 ( v ) − 2 x . \mathrm{pre}(v)=F_0(v)-2x. pre(v)=F0(v)−2x.
令候选集合 C = { x i } C=\{x_i\} C={xi},初始时 x i = i + 1 , 1 ≤ i ≤ n − 1 x_i=i+1,1\le i \le n-1 xi=i+1,1≤i≤n−1 。
进行二分,取一半候选集合为 S = { x i } , 1 ≤ i < ∣ C ∣ 2 S=\{x_i\} ,1\le i\lt \frac{|C|}{2} S={xi},1≤i<2∣C∣ ,另一半为 $T={x_i},\frac{|C|}{2}\le i\le |C| $
询问? 1 |S| S
,得到实测总和
r
e
a
l
real
real,计算预测总和
e
x
=
∑
v
∈
S
f
(
v
)
ex=\sum_{v\in S}f(v)
ex=∑v∈Sf(v) 。
如果 r e a l − e x = 2 x real-ex=2x real−ex=2x , 则根在集合 S S S,否则在 T T T。更新 C C C 为对应子集,直到 ∣ C ∣ = 1 |C|=1 ∣C∣=1 。此时唯一元素即为隐藏根,再令 v a l ( r t ) = F 0 − x \mathrm{val}(\mathrm{rt})=F_0-x val(rt)=F0−x , 连同先前存储的其他节点 v a l ( v ) \mathrm{val}(v) val(v) 一并输出。
最多查询次数 1 + ( n − 1 ) + 1 + 1 + ⌈ log 2 ( n − 1 ) ⌉ ≤ n + 12 < n + 200 1+(n-1)+1+1+\lceil\log_2(n-1)\rceil \le n+12 < n+200 1+(n−1)+1+1+⌈log2(n−1)⌉≤n+12<n+200 。
代码实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
ll op1(const vi &v)
{
int n = v.size();
cout << "? 1 " << n << " ";
for (int i = 0; i < n; i++)
{
cout << v[i] << " \n"[i == n - 1];
}
cout << flush;
ll res;
cin >> res;
return res;
}
void op2(int u)
{
cout << "? 2 " << u << "\n";
cout << flush;
}
void solve()
{
int n;
cin >> n;
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
}
ll f0 = op1({1});
vl f(n + 1);
for (int i = 2; i <= n; i++)
{
f[i] = op1({i});
}
op2(1);
ll f1 = op1({1});
ll x = (f0 - f1) / 2;
vl ans(n + 1);
ans[1] = -x;
for (int i = 2; i <= n; i++)
{
ans[i] = f[i] - f0;
}
if (f0 == x)
{
cout << "! ";
for (int i = 1; i <= n; i++)
{
cout << ans[i] << " \n"[i == n];
}
cout << flush;
return;
}
vi C;
for (int i = 2; i <= n; i++)
{
C.push_back(i);
}
auto calc = [&](int v) -> ll
{
return f[v] - 2 * x;
};
while (C.size() > 1)
{
int m = C.size() / 2;
vi S(C.begin(), C.begin() + m);
ll sum = 0;
for (auto &x : S)
{
sum += calc(x);
}
ll aim = S.empty() ? 0 : op1(S);
if (aim == sum)
C.erase(C.begin(), C.begin() + m);
else
C.resize(m);
}
int rt = C[0];
ans[rt] = f0 - x;
cout << "! ";
for (int i = 1; i <= n; i++)
{
cout << ans[i] << " \n"[i == n];
}
cout << flush;
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接: