Codeforces Round 1061 (Div. 2) 做题记录
很显然,其实每次让浩拿一个,每次让亚历克斯拿一个,剩下的留到第二天绝对不会比其他解差,所以,分类讨论,当 \(n\) 是偶数的时候:
这个三元序列的长度,就是 \(n\) 是偶数时浩能拿到的最大数量。那么显然答案是 \(\frac{n}{2}-1\)。
当 \(n\) 是奇数的时候:
这个三元序列的长度,就是 \(n\) 是奇数时浩能拿到的最大数量。显然答案是 \(\lfloor\frac{n}{2}\rfloor\)。
这里已经可以做了,但是可以更简单。
发现不管 \(n\) 是偶数还是奇数,答案都可以通用:
代码:
#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;
}
首先看到 \(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\),所以特判即可通过。
代码:
#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;
}
这题就很数学了,很练思维。
首先当一个数是 \(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 \bmod g,x-2g-x \bmod g)\),然后很显然满足 \(g \le g+x \bmod g \le x-2g-x \bmod g\),并且 \(g\) 和 \(x-2g-x \bmod 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 \not\equiv 0 \pmod g\),因为如果 \(x \ge 4g\) 或者 \(x \equiv 0 \pmod g\),那 \(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\),它可行当且仅当:
然后你会发现可以用一个桶来记录每个数出现次数,那么就解决了第 \(2 \sim 4\) 个式子,然后第 \(1\) 个也就是加一个前缀和就行了。
所以说遍历 \(g = [1,n]\),然后逐个判断是否可行即可。
代码:
#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;
}
喜提 \(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\) 位的方法很简单,根据简单原理得出:
然后仔细观察,发现可以用一种神奇的方法给等式右边同时减去那些 \(1 \sim i-1\) 位有和 \(p_n\) 不相同的的数,你就会得到这个:
这里 \(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\) 所构成的集合。
然后你就会发现,这个东西的询问次数其实是:
虽然这玩意看似在 \(n\) 比较极限的时候会超出 \(2n\) 一点点,但这只是很粗略的上界,实际明显低于这个。
代码:
#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;
}
我真是太菜了……
首先看到是博弈,然后很容易想到这道题是个 Ad-hoc。仔细观察,显然答案具有单调性,设亚历克斯要拼命将美化值调到 $ \ge g$,浩要拼命将美化值弄到 \(<g\),假设亚历克斯先走,那么什么情况浩会输?又什么情况浩会赢呢?其实显然,就是设 \(f_a(g,i)\) 表示 \(i\) 在 \(a\) 数组中有多少个 \(j(1 \le j \le n,j \not= i)\),满足 \(a_{\max(i,j)}-a_{\min(i,j)} \ge g\)。那么稍微思考即可得出如果 \(\exists i,f_a(g,i) = 2\),这样亚历克斯第一步禁锢了 \(i\) 之后,反正 \(i\) 有两个匹配 \(j\) 和 \(j'\),不管浩删掉哪个亚历克斯都可以选择另一个来使得美化值 $ \ge g$,也就是浩输了,否则的话,那么浩就赢了。简单推广,发现如果是浩先走(也就是题目所说)其实也没什么区别,容易知道,假设浩第一步选了 \(p\),那么只有 \(q(1 \le q \le n,q \not=p),a_{\max(p,q)}-a_{\max(p,q)} \ge g\) 的 \(q\) 对应的 \(f_a(g,q)\) 会减去 \(1\),那么思路呼之欲出。先枚举 \(p\),然后找到所有的 \(q\),直接判断这些 \(q\) 对应的 \(f_a(g,q)\) 是不是 $ \le 2$,如果是的话那就没问题,当然你会发现……这样相当麻烦,并且复杂度也没有保证,咋办?显然发现,诶,我们不是只需要判断 \(f_a(g,i)\) 是不是 \(>2\) 或者 \(=2\) 或 \(=1\) 或 \(=0\) 而已,何必准确?那可能有人问了,那这样找到的 \(q\) 就不是全部的了,没法和算出的对浩有威胁的点的数量进行比较,其实……对浩有威胁的点的数量也可以只是不准确的,跟 T4 怎么说呢,有点像,就是那种同时抛弃的感觉,这样可以使正确性没有问题。当然了,这里我们 \(f\) 显然需要 vector(虽然非人哉出题人让非 ,话说出题人你太有生活了),然后没必要知道值,直接看前三小记录,然后大小就是值,并且可以同时找出这个点对应哪些点满足,这里顺便维护前三小是 vector 的神奇玩意也水过了set 比较方便,但是由于 set 有 STL 自带常数并且复杂度不算常数也是单次操作 \(O(\log n)\) 的,这样会使复杂度变成 \(O(n \log n \log V)\),不是最优解法。发现维护前三小可以用变量单次 \(O(1)\) 维护(只不过稍微有亿点难写,本人调了很久结果初值忘了加 \(1\),我真的很有生活了)。
set 的 \(O(n \log n \log V)\) 写法:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int a[N];
vector<int>ss[N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while(_--)
{
int n,maxx = 0;
cin >> n;
for(int i = 1;i<=n;++i)
{
cin >> a[i];
maxx = max(maxx,a[i]);
}
int l = -maxx,r = maxx,ans = 0;
while(l<=r)
{
int mid = l+r>>1;
for(int i = 1;i<=n;++i)
{
ss[i].clear();
}
set<pair<int,int>>s;
for(int i = 1;i<=n;++i)
{
for(auto [x,y]:s)
{
if(a[i]-x>=mid)
{
ss[i].push_back(y);
ss[y].push_back(i);
}
}
s.insert({a[i],i});
if(s.size()>3)
{
s.erase(prev(s.end()));
}
}
int w = 0;
for(int i = 1;i<=n;++i)
{
w+=(ss[i].size()>=2);
}
int flag = 1;
for(int i = 1;i<=n;++i)
{
int si = ss[i].size();
int b = (si>=2);
for(int c:ss[i])
{
b+=(ss[c].size() == 2);
}
if(b == w)
{
flag = 0;
break;
}
}
if(flag)
{
ans = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
cout << ans << '\n';
}
return 0;
}
[AC 记录。]
(https://codeforces.com/contest/2156/submission/349097585)
变量维护写法(\(O(n \log V)\)):
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int a[N];
vector<int>ss[N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while(_--)
{
int n,maxx = 0;
cin >> n;
for(int i = 1;i<=n;++i)
{
cin >> a[i];
maxx = max(maxx,a[i]);
}
int l = -maxx,r = maxx,ans = 0;
while(l<=r)
{
int mid = l+r>>1;
for(int i = 1;i<=n;++i)
{
ss[i].clear();
}
int minn = maxx+1,minnx = 0,cmin = maxx+1,cminn = 0,tmin = maxx+1,tminn = 0;
for(int i = 1;i<=n;++i)
{
if(a[i]-minn>=mid&&minnx)
{
ss[i].push_back(minnx);
ss[minnx].push_back(i);
}
if(a[i]-cmin>=mid&&cminn)
{
ss[i].push_back(cminn);
ss[cminn].push_back(i);
}
if(a[i]-tmin>=mid&&tminn)
{
ss[i].push_back(tminn);
ss[tminn].push_back(i);
}
if(tmin>a[i])
{
tmin = a[i];
tminn = i;
}
if(cmin>tmin)
{
cmin^=tmin^=cmin^=tmin;
cminn^=tminn^=cminn^=tminn;
}
if(minn>cmin)
{
minn^=cmin^=minn^=cmin;
minnx^=cminn^=minnx^=cminn;
}
}
int w = 0;
for(int i = 1;i<=n;++i)
{
w+=(ss[i].size()>=2);
}
int flag = 1;
for(int i = 1;i<=n;++i)
{
int si = ss[i].size();
int b = (si>=2);
for(int c:ss[i])
{
b+=(ss[c].size() == 2);
}
if(b == w)
{
flag = 0;
break;
}
}
if(flag)
{
ans = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
cout << ans << '\n';
}
return 0;
}
本人已经累趴,不想动了。

浙公网安备 33010602011771号