T1
很显然,其实每次让浩拿一个,每次让亚历克斯拿一个,剩下的留到第二天绝对不会比其他解差,所以,分类讨论,当 \(n\) 是偶数的时候:
\[(1,1,n-2),(1,1,n-4),(1,1,n-6),...,(1,1,2) \]
这个三元序列的长度,就是 \(n\) 是偶数时浩能拿到的最大数量。那么显然答案是 \(\frac{n}{2}-1\)。
当 \(n\) 是奇数的时候:
\[(1,1,n-2),(1,1,n-4),(1,1,n-6),...(1,1,1) \]
这个三元序列的长度,就是 \(n\) 是奇数时浩能拿到的最大数量。显然答案是 \(\lfloor\frac{n}{2}\rfloor\)。
这里已经可以做了,但是可以更简单。
发现不管 \(n\) 是偶数还是奇数,答案都可以通用:
\[\left\lfloor\frac{n-1}{2}\right\rfloor \]
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while(_--)
{
int n;
cin >> n;
cout << (n-1>>1) << '\n';
}
return 0;
}
T2
首先看到 \(n \le 20\),虽然 \(a_i \le 10^9\),但是你会发现当 \(1 \sim n\) 中只要有一台 B 机器,那么这一次轮回至少除以 \(2\),所以复杂度最多是 \(O(qn \log V)\),\(V\) 是值域,但你会发现如果 \(1 \sim n\) 中全都是 A 机器,那么这么做,复杂度就是 \(O(qV)\) 的,会 T 得飞起,但是你会发现如果 \(1 \sim n\) 中全都是 A 机器的话,那对于任意 \(a_i\) 答案显然是 \(a_i\),所以特判即可通过。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
char s[25];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while(_--)
{
int n,q;
cin >> n >> q;
cin >> s+1;
int A = 0,B = 0;
for(int i = 1;i<=n;++i)
{
A+=s[i] == 'A';
B+=s[i] == 'B';
}
while(q--)
{
int x;
cin >> x;
if(B == 0)
{
cout << x << '\n';
}
else
{
int num = 0;
while(x)
{
for(int i = 1;i<=n;++i)
{
if(!x)
{
break;
}
++num;
if(s[i] == 'A')
{
--x;
}
else
{
x>>=1;
}
}
}
cout << num << '\n';
}
}
}
return 0;
}
T3
这题就很数学了,很练思维。
首先当一个数是 \(g\) 的倍数时,它不需要做任何操作,这是显然的。
然后给出一些结论:
- 我们完全可以先 删除数再分裂数,答案不会更劣。
- 设我们要使所有数都是 \(g\) 的倍数,对于一个数 \(x\) 它如果 \\ge 4g,那它一定 可以通过分裂来使得分成的两个数都是 \(g\) 的倍数。
- 如果 \(x < 4g\),并且 \(x\) 不是 \(g\) 的倍数,那么 \(x\) 一定没有办法 通过分裂来使得分成的两个数都是 \(g\) 的倍数,所以只能删除。
然后来证明:
- 采用反证法,假设有一个最优解,对于某个数 \(x\),它将 \(x\) 分成了 \(x_1,x_2,x_3\),然后删除了 \(x_1\) 或 \(x_3\),那么我们完全可以先删掉 \(x\),然后就不会有分裂操作,这样的话这个序列相当于少了一个数(\(x_1\) 或 \(x_2\)),对于这道题,少了一个数只可能更优或相等,但不会更劣。
- 我们完全可以将 \(x\) 拆成 \((g,g+x \mod g,x-2g-x \mod g)\),然后很显然满足 \(g \le g+x \mod g \le x-2g-x \mod g\),并且 \(g\) 和 \(x-2g-x \mod g\) 显然都是 \(g\) 的倍数。
- 使用数学归纳法,设 \(P_x\) 表示这个命题对于 \(x\) 的真假,那么 \(P_1\) 肯定成立,因为 \(1\) 无法分割,那么考虑 \(P_x\),并且 \(\forall_{i(1 \le i \le x-1)} P_{i} = 1\),当然 \(2 \le x < 4g,x \mod g \not = 0\),因为如果 \(x \ge 4g\) 或者 \(x \mod g = 0\),那 \(P_x\) 自然成立(前面已经证明),没意义。假设我们将 \(x\) 分成 \(x_1,x_2,x_3\),并且 \(x_1+x_2+x_3 = x\),那根据前面说的,\(P_{x_1}\) 和 \(P_{x_3}\) 都是无法分割使得分成的两个数都是 \(g\) 的倍数,所以想要证反,只有可能 \(x_1\) 和 \(x_3\) 都是 \(g\) 的倍数,如果 \(x_1 \ge 2g\),则 \(x_3 \ge x_2 \ge x_1\),那么 \(x = x_1+x_2+x_3 \ge 2g+2g+2g \ge 6g\),显然和 \(x<4g\) 矛盾,因此,唯一的可能是 \(x_1 = g\)。假设 \(x_3 = g\),那么根据 \(x_1 \le x_2 \le x_3\) 得出 \(x_1 = x_2 = x_3 = g\),那么 \(x = 3g\),与 \(x\) 不是 \(g\) 的倍数矛盾。最后就是 \(x_3>2g\),于是 \(x = x_1+x_2+x_3 \ge g+g+2g = 4g\),与 \(x<4g\) 矛盾。
证明完后, 你会发现,对于一个 \(g\),它可行当且仅当:
\[[x \ge g]+[x = g]+[x = 2g]+[x = 3g] \ge n-k \]
然后你会发现可以用一个桶来记录每个数出现次数,那么就解决了第 \(2 \sim 4\) 个式子,然后第 \(1\) 个也就是加一个前缀和就行了。
所以说遍历 \(g = [1,n]\),然后逐个判断是否可行即可。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int pre[N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while(_--)
{
int n,k;
cin >> n >> k;
for(int i = 1;i<=n;++i)
{
a[i] = 0;
}
for(int i = 1;i<=n;++i)
{
int x;
cin >> x;
++a[x];
}
for(int i = 1;i<=n;++i)
{
pre[i] = pre[i-1]+a[i];
}
int ans;
for(int i = 1;i<=n;++i)
{
if(n-pre[min(n,(i<<2)-1)]+a[i]+((i<<1)>n?0:a[i<<1])+(i*3>n?0:a[i*3])>=n-k)
{
ans = i;
}
}
cout << ans << '\n';
}
return 0;
}
T4
喜提 \(8\) 个 WA!
我们很显然有一个 \(n (\lfloor \log n \rfloor+1)\) 次查询的做法,设 \(f_k(x)\) 表示 \(x\) 的第 \(k\) 位的值,显然我们只需要求 \(p_n\) 的所有第 \(k(0 \le k \le \lfloor \log n \rfloor)\) 就能知道 \(p_n\) 的值,求出 \(p_n\) 第 \(k\) 位的方法很简单,根据简单原理得出:
\[f_k(p_n)=\sum_{i = 1}^n f_k(i)-\sum_{i = 1}^{n-1} f_k(p_i) \]
然后仔细观察,发现可以用一种神奇的方法给等式右边同时减去那些 \(1 \sim i-1\) 位有和 \(p_n\) 不相同的的数,你就会得到这个:
\[f_k(p_n) = \sum_{i \in S_{k-1}} f_k(i)-\sum_{i \in P_{k-1}} f_k(p_i) \]
这里 \(S_x\) 指的是 \(1 \sim x-1\) 都和 \(p_n\) 相同的数所构成的集合,\(P_x\) 指的是 \(1 \sim x-1\) 都和 \(p_n\) 相同的 \(p_i(1 \le i \le n-1)\) 的下标 \(i\) 所构成的集合。
然后你就会发现,这个东西的询问次数其实是:
\[\sum_{k = 0}^{\lfloor \log_2 n \rfloor} \left\lceil \frac{n-1}{2^k} \right\rceil \]
\[= (n-1)+\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \left\lceil \frac{n-1}{2^k} \right\rceil \]
\[\le (n-1)+\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \frac{n-1}{2^k}+1 \]
\[\le (n-1)+(n-1)\sum_{k = 1}^{\lfloor \log_2 n \rfloor} \frac{1}{2^k}+\sum_{k = 1}^{\lfloor \log_2 n \rfloor}1 \]
\[\le (n-1)+(n-1)(1-\frac{1}{2^{\lfloor \log_2 n \rfloor}})+\lfloor \log_2 n \rfloor \]
\[\le (n-1)+(n-1)(1-\frac{1}{2^{\lfloor \log_2 n \rfloor}})+\lfloor \log_2 n \rfloor \]
\[\le (n-1)+(n-1)(1-\frac{1}{n})+\lfloor \log_2 n \rfloor(\operatorname{beacause} 2^{\lfloor \log_2 n \rfloor} \le n) \]
\[\le (n-1)+(n-1)-\frac{n-1}{n}+\lfloor \log_2 n \rfloor \]
\[\le 2n-2-\frac{n-1}{n}+\lfloor \log_2 n \rfloor \]
\[\le 2n-2-(1-\frac{1}{n})+\lfloor \log_2 n \rfloor \]
\[\le 2n-3+\frac{1}{n}+\lfloor \log_2 n \rfloor \]
虽然这玩意看似在 \(n\) 比较极限的时候会超出 \(2n\) 一点点,但这只是很粗略的上界,实际明显低于这个。
代码:
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+5;
const int mx = 2e4;
int a[N];
int b[N];
int bx[N];
int by[N];
int P[N];
int hou[N];
int dian[N];
int NP[N][2];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
for(int i = 1;i<=mx;++i)
{
int s = (i&1);
b[i] = b[i-1]+s;
bx[i] = bx[i-1];
by[i] = by[i-1];
if(s)
{
bx[i]+=((i&2)>0);
}
else
{
by[i]+=((i&2)>0);
}
}
int _;
cin >> _;
while(_--)
{
int n;
cin >> n;
int cnt = 0,numx = 0;
for(int i = 1;i<n;++i)
{
cout << "? " << i << " 1" << endl;
cin >> dian[i];
cnt+=dian[i];
}
int w = b[n]-cnt;
hou[0] = w;
for(int i = 1;i<n;++i)
{
if(dian[i] == w)
{
P[++numx] = i;
}
}
int ans = w;
int s = 31-__builtin_clz(n);
int num1 = 0,num2 = (w == 0?by[n]:bx[n]);
int nx = 0,ny = 0;
for(int i = 1;i<=numx;++i)
{
cout << "? " << P[i] << " 2" << endl;
int x;
cin >> x;
num1+=(x>0);
if(!x)
{
NP[++nx][0] = P[i];
}
else
{
NP[++ny][1] = P[i];
}
}
for(int i = 1;i<=s;++i)
{
ans+=(1<<i)*(num2-num1);
if(i == s)
{
continue;
}
w = num2-num1;
hou[i] = w;
for(int j = 1;j<=(w?ny:nx);++j)
{
P[j] = NP[j][w];
}
numx = (w?ny:nx);
nx = 0,ny = 0;
num1 = 0;
for(int j = 1;j<=numx;++j)
{
cout << "? " << P[j] << ' ' << (1<<i+1) << endl;
int x;
cin >> x;
num1+=(x>0);
if(!x)
{
NP[++nx][0] = P[j];
}
else
{
NP[++ny][1] = P[j];
}
}
num2 = 0;
for(int j = 1;j<=n;++j)
{
int dui = 1;
for(int k = 0;k<=i;++k)
{
int flag = ((j&(1<<k))>0);
if(flag!=hou[k])
{
dui = 0;
break;
}
}
if(dui)
{
num2+=((j&(1<<i+1))>0);
}
}
}
cout << "! " << ans << endl;
}
return 0;
}