VP比赛补题报告之“Codeforces Round 766 (Div. 2)”
比赛链接
VP成绩

比赛经过
\(\text{A}\) 题一眼看过去,手捏几组小数据规律就出来了,证明极水,\(7\) \(\text{min}\) 就无伤 \(\text{AC}\) 掉了。
\(\text{B}\) 题以为是个纯数学题,推式子浪费我 \(20\) ~ \(30\) \(\text{min}\)。后面仔细想了想最优位置的分布情况,发现枚举即可,跳出圈子写代码,第 \(44\) \(\text{min}\) 切掉了。(显然是慢了!)
\(\text{C}\) 题一眼小学奥数,巧妙借助唯一的偶素数 \(2\) 即可,思路 \(5\) \(\text{min}\) 就出来了,但代码实现上花了很多时间,理论上是写复杂了,导致用了 \(27\) 分钟才把代码实现完。运气不错的是一次就无伤 \(\text{AC}\) 掉了,第 \(76\) \(\text{min}\) 切掉了。
\(\text{D}\) 题看了看数据范围。先尝试了线性 \(\text{DP}\),发现转移的复杂度降不下来(甚至不如暴力),弃掉这个做法了,此时因为没及时跳出来,花了 \(5\) ~ \(10\) \(\text{min}\)。后面才发现了值域的范围,然后就类似于以前做过的一道二进制的题目,那道大概是求数列里任意两个数进行某种位运算的最大值(改成几种不同的方案就类似这题了),然后就开始枚举值域来判断了,简单推导就发现判断复杂度可以降成 \(\text{log}\) 级别的,代码很好写,第 \(104\) \(\text{min}\) 无伤 \(\text{AC}\) 掉了。
\(\text{E}\) 题刚看完题就只剩 \(10\) \(\text{min}\)。剩下的时间觉得是个二维线性 \(\text{DP}\),但时间复杂度会炸裂,然后不知道咋搞,罚坐到 \(\text{120}\) \(\text{min}\)。比赛结束(是的,\(\text{F}\) 题都没开题)。
赛后补题+分析
A. Not Shading
简要/形式化题意
一个 \(n\) 行 \(m\) 列的 \(01\) 矩阵 \(\{a_{i,j}\}\)。对于每个 \(a_{i,j}=1\) 可进行两种操作:
1.对于所有 \(1 \le k \le n\),让 \(a_{k,j}=1\)。
2.对于所有 \(1 \le k \le m\),让 \(a_{i,k}=1\)。
问:至少几次操作后使得对于给定的 \(r\),\(c\),满足 \(a_{r,c}=1\)。
题解
(以下为严格证明,考场上手捏小数据更快)
情况 \(1\):原始矩阵中 \(a_{r,c}=1\),答案为 \(0\)。
情况 \(2\):原始矩阵中不存在 \(a_{i,j}\) 使得 \(a_{i,j}=1\),答案为 \(-1\)。
情况 \(3\):存在 \(1 \le k \le n\),使得 \(a_{k,c}=1\) 或存在 \(1 \le k \le m\),使得 \(a_{r,k}=1\),答案为 \(1\)。
\(\text{otherwise}\): 倒序思考。若最终 \(a_{r,c}=1\)。则在进行最后一步操作之前,必然存在 \(1 \le k \le n\),使得 \(a_{k,c}=1\)(称为 \(\text{A}\) 局面) 或存在 \(1 \le k \le m\),使得 \(a_{r,k}=1\)(称为 \(\text{B}\) 局面)。在倒数第二步任意选取一个 \(a_{k_1,k_2}=1\) 执行 \(1\) 操作,此时因为 \(1 \le r \le n\),所以 \(a_{r,k_2}=1\),于是 \(\text{B}\) 局面成立,由情况 \(3\),答案为 \(2\)。在倒数第二步任意选取一个 \(a_{k_1,k_2}=1\) 执行 \(2\) 操作,此时因为 \(1 \le c \le m\),所以 \(a_{k_1,c}=1\),于是 \(\text{A}\) 局面成立,由情况 \(3\),答案为 \(2\)。综上,答案为 \(2\)。
时间复杂度:\(O(nm)\)。
AC code
#include<bits/stdc++.h>
using namespace std;
const int N=60;
int T,n,m,r,c,cnt;
char ch[N][N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>T;
while(T--) {
cin>>n>>m>>r>>c;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>ch[i][j];
if(ch[r][c]=='B') {
cout<<0<<endl;
continue;
}
cnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cnt+=(ch[i][j]=='B');
if(!cnt) {
cout<<-1<<endl;
continue;
}
cnt=0;
for(int i=1;i<=n;i++) cnt+=(ch[i][c]=='B');
for(int j=1;j<=m;j++) cnt+=(ch[r][j]=='B');
if(!cnt) {
cout<<2<<endl;
continue;
}
cout<<1<<endl;
}
return 0;
}
B. Not Sitting
简要/形式化题意
在 \(n \times m\) 的全 \(0\) 矩阵 \(\{A_{i,j}\}\) 中,记 \(d(a,b,c,d)=\left\vert a-c \right\vert + \left\vert b-d \right\vert\)。甲和乙博弈。
第一轮:甲在矩阵中选取 \(k\) 个点对 \((i,j)\),让 \(A_{i,j}=1\)。
第二轮:乙在矩阵中选取 \(1\) 个点对 \((a,b)\),满足 \(A_{a,b}=0\)。
第三轮:甲在矩阵中选取 \(1\) 个点对 \((c,d)\)。
甲以 \(d(a,b,c,d)\) 最大为最优,乙以 \(d(a,b,c,d)\) 最小为最优。甲乙均采用最优决策。
求对于所有整数 \(k\),其中 \(0 \le k \le n \times m-1\),博弈的结果 \(d(a,b,c,d)\) 的值。
题解
最后一轮的决策显然比较好讨论,因为第二轮已经确定。那么对于第三轮,为使 \(d(a,b,c,d)\) 尽可能大,即 \(\left\vert a-c \right\vert + \left\vert b-d \right\vert\) 尽可能大,则 \(c\) 取极值(\(1\) 或 \(n\)),\(d\) 取极值(\(1\) 或 \(m\))。因而得到第三轮只有四种可能的最优决策:\((1,1)\),\((1,m)\),\((n,1)\),\((n,m)\),从而 \(d(a,b,c,d)=\max \{a+b-2,a-1+m-b,n-a+b-1,n-a+m-b\}\)。
由特殊到一般,考虑 \(k=0\) 时,这时由刚刚的结论可得对于一个确定 \((a,b)\),其博弈结果可以 \(O(1)\) 确定,那么只要 \(O(nm)\) 的暴力求出 \(\min\limits_{1 \le a \le n,1 \le b \le m}\{\max \{a+b-2,a-1+m-b,n-a+b-1,n-a+m-b\}\}\),就得到了最优博弈结果,记这个最优决策为 \((a',b')\)。
当 \(k=1\) 时,第一轮为了让第二轮选不到 \((a',b')\),必然会让 \(A_{a',b'}=1\),从而第二轮的最优决策博弈结果为 \(\min\limits_{1 \le a \le n,1 \le b \le m,(a,b) \ne (a',b')}\{\max \{a+b-2,a-1+m-b,n-a+b-1,n-a+m-b\}\}\),可以发现其实就是次小值。
以此类推,记矩阵 \(\{B_{i,j}\}\),其中 \(B_{i,j}=\max \{i+j-2,i-1+m-j,n-i+j-1,n-i+m-j\}\),则最优博弈结果为 \(\{B_{i,j}\}\) 中的第 \((k+1)\) 小。原问题转化为将 \(\{B_{i,j}\}\) 内的数据从小到大排序输出即可,优先队列即可 \(O(nm\log{nm})\) 解决,桶排序可做到 \(O(nm)\)。
AC code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,n,m;
int cnt;
priority_queue<int>q;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>T;
while(T--) {
cin>>n>>m;
cnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
int num=0;
num=max(num,i+j-2);
num=max(num,i-1+m-j);
num=max(num,n-i+j-1);
num=max(num,n-i+m-j);
q.push(-num);
}
while(!q.empty()) {
cout<<-q.top()<<" ";
q.pop();
}
cout<<endl;
}
return 0;
}
C. Not Assigning
简要/形式化题意
给定一个无边权无根树,请为每条边赋边权,使得所有边数不大于 \(2\) 的树链中,边权和为质数。
题解
对于所有边数为 \(1\) 的树链,显然,此边权一定是质数,因此树上的所有边的边权均为质数。
对于所有边数为 \(2\) 的树链,需要满足两条边权相加仍然是质数,即质数加质数仍为质数。如果是两个奇(偶)质数相加必然会得到一个大于 \(2\) 的偶数(合数,舍去)。那么必然一个是偶素数 \(2\),另一个是奇质数 \(p\),满足 \(p+2 \in \mathbb{P}\)。
对于一个度数为 \(3\) 的点 \(k\),设与其相邻的三个点为 \(a,b,c\)。则 \(a \rightarrow k \rightarrow b\)(树链 \(1\)),\(a \rightarrow k \rightarrow c\)(树链 \(2\)),\(b \rightarrow k \rightarrow c\)(树链 \(3\)),三条树链必然都只包含一个边权 \(2\)。如果 \(a \rightarrow k\) 的边权为 \(2\),则在树链 \(1\) 中,\(k \rightarrow b\) 不为 \(2\),在树链 \(2\) 中,\(k \rightarrow c\) 不为 \(2\),那么与树链 \(3\) 的约束条件矛盾。同理可证其他情形均不成立。当度数大于 \(3\) 时,由于包含了度数为 \(3\) 这个子集,故也是无解。综上可得,有解的充要条件为树的形态是一条链。
那么一个简单的构造方案是从度数为 \(1\) 的点开始遍历整条链,按 \(2,p,2,p\cdots\) 的顺序给边赋边权。由于是构造一组可行解,这里就取 \(p=3\) 了。
AC code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int T,n,u,v,ans[N];
int deg[N],flag[N];
map<pair<int,int>,int>Map;
vector<int>G[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>T;
while(T--) {
memset(deg,0,sizeof(deg));
memset(flag,0,sizeof(flag));
cin>>n;
for(int i=1;i<=n;i++) G[i].clear();
for(int i=1;i<n;i++) {
cin>>u>>v;
if(u>v) swap(u,v);
Map[make_pair(u,v)]=i;
G[u].push_back(v);
G[v].push_back(u);
deg[u]++;
deg[v]++;
}
bool f=false;
for(int i=1;i<=n;i++)
if(deg[i]>=3) {
cout<<-1<<endl;
f=true;
break;
}
if(f) continue;
int st;
for(int i=1;i<=n;i++)
if(deg[i]==1) {
st=i;
break;
}
int cnt=0,pre;
while(cnt<=n-1) {
cnt++;
pre=st;
flag[pre]=1;
for(int i=0;i<G[pre].size();i++) {
int now=G[pre][i];
if(flag[now]) continue;
st=now;
}
if(cnt&1) ans[Map[make_pair(min(st,pre),max(st,pre))]]=2;
else ans[Map[make_pair(min(st,pre),max(st,pre))]]=3;
}
for(int i=1;i<n;i++) cout<<ans[i]<<" ";
cout<<endl;
for(int i=1;i<=n;i++)
for(int j=0;j<G[i].size();j++)
Map[make_pair(min(i,G[i][j]),max(i,G[i][j]))]=0;
}
return 0;
}
D. Not Adding
简要/形式化题意
给定长度为 \(n\) 的序列 \(a\),\(a\) 的数据两两不同。每次操作可选择一个数对 \((a_i,a_j)\),把 \(\gcd\{a_i,a_j\}\) 加到序列 \(a\) 的末尾,前提是 \(\gcd\{a_i,a_j\}\) 不在加之前的 \(a\) 数组中。问:最多能进行几次操作。
题解
题意很迷人,稍加转化可知,原问题是在问 \(a\) 数组中所有子序列的 \(\gcd\) 能形成几个不同的且不属于原始 \(a\) 数组的数。显然这个值域范围给了很好的提示。我们反向考虑,枚举值域内的数 \(p\),判断其是否可以形成。
首先,可以形成 \(p\) 的数必然是 \(p\) 的倍数。筛出所有的原始 \(a\) 数组中 \(p\) 的倍数,设这个集合为 \(S\)。对于集合 \(S\) 的子集 \(S'\),它所有数的 \(\gcd\) 必然不小于 \(S\) 中所有数字的 \(\gcd\)。然而他们都不会小于 \(p\)(因为 \(p\) 是他们的公约数)。因此考虑全集 \(S\) 的 \(\gcd\) 是否等于 \(p\) 即可,如果不是,显然他的子集也不可能,如果是,则 \(p\) 可以被形成。
那么瓶颈就在于如何高效的筛出原始 \(a\) 数组中 \(p\) 的倍数,由于值域不大,直接记录一个桶即可,这样枚举倍数是 \(\log\) 级别的。总体时间复杂度为 \(O(V \log V)\)。
AC code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],b[N],ans;
int gcd(int a,int b) {
return b?gcd(b,a%b):a;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) {
cin>>a[i];
b[a[i]]=1;
}
for(int i=1;i<N;i++) {
int cnt=0,ex;
if(b[i]) continue;
for(int j=2;i*j<N;j++)
if(b[i*j]) {
cnt++;
if(cnt==1) ex=i*j;
else ex=gcd(ex,i*j);
}
if(ex==i) ans++;
}
cout<<ans;
return 0;
}
E. Not Escaping
简要/形式化题意
给定一个 \(n \times m\) 的矩阵,长度为 \(n\) 的数组 \(x\),\(k\) 个有序五元组 \((a,b,c,d,h)\),满足 \(a<c\)。选择一条从 \((1,1)\) 到 \((n,m)\) 的路径,开始时,代价为 \(0\)。该路径上,对于从 \((a,b)\) 直接到 \((c,d)\),若 \(a \ne c\),则存在 \(h\) 使得 \((a,b,c,d,h)\) 出现在有序五元组集合中,代价减 \(h\)。若 \(a = c\),\(b\),\(d\) 无限制,代价加 \(\left\vert b-d \right\vert \times x_a\)。问路径结束后代价的最小值。
题解
显然可以看出是一道二维线性 \(\text{DP}\) 题。记 \(dp_{i,j}\) 表示从 \((1,1)\) 到 \((i,j)\) 的合法路径的最小代价。
对于同行的,把绝对值拆掉:当 \(b>d\) 时,代价加 \((b-d) \times x_a\),当 \(b<d\) 时,代价加 \((d-b) \times x_a\),两者的最小值即为答案,故从左到右和从右到左两次转移即可,转移方程如下:
\(dp_{i,j}=\min\limits_{1 \le j' <j}\{dp_{i,j},dp_{i,j'}+(j-j') \times x_i\}\)。
\(dp_{i,j}=\min\limits_{j < j' \le m}\{dp_{i,j},dp_{i,j'}+(j'-j) \times x_i\}\)。
优化:对于决策点 \(j_1\),\(j_2\) 满足 \(j_1 < j_2 < j\)。如果 \(j_1\) 是 \(j\) 的最优决策点。则
\(dp_{i,j_1}+(j-j_1) \times x_i = dp_{i,j_1}+(j_2-j_1) \times x_i+(j-j_2) \times x_i< dp_{i,j_2}+(j-j_2) \times x_i\)。
得出:\(dp_{i,j_1}+(j_2-j_1) \times x_i < dp_{i,j_2}\)。与 \(dp_{i,j_2}\) 的最优性质矛盾。因此可能作为最优决策点的只有 \(j\) 左侧第一个点和 \(j\) 右侧第一个点,改写之后为。
\(dp_{i,j}=\min\{dp_{i,j},dp_{i,j-1}+x_i\}\)。
\(dp_{i,j}=\min\{dp_{i,j},dp_{i,j+1}+x_i\}\)。
考虑跨行的情况,对于有序五元组 \((a,b,c,d,h)\) 来说。
\(dp_{c,d}=\min\{dp_{c,d},dp_{a,b}-h\}\)。
那么这样做的时间复杂度 \(O(nm)\) 的,无法通过此题。仔细思考一下行内的转移式子。我们发现,如果没有出现在有序五元组中的点对,他在转移之前的值为 \(\text{inf}\)(初始时,除了\(dp_{1,1}=0\),\(dp_{i,j}\) 都为 \(\text{inf}\))。故不可能作为最优决策点。也就是说,实际上只有 \(2k\) 个有效点加上起始点一共 \(2k+2\) 个。我们只要对这些点进行 \(\text{DP}\) 即可。实现的时候要多一步排序,也是这个算法的瓶颈,所以时间复杂度 \(O(n \log k)\)。
当然,状态的设计要改一改,记 \(dp_i\) 为 \((1,1)\) 到第 \(i\) 个有效点的最小代价,然后维护一下编号之间的关系,实现起来略微复杂。
AC code
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
using namespace std;
const int N=1e5+10;
int t,n,m,k,a,b,c,d,h,x[N];
vector<pair<int,int> >E1[N];
pair<int,int>F[3*N];
int dp[3*N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--) {
int cnt=0;
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>x[i];
for(int i=1;i<=n;i++) E1[i].clear();
E1[1].push_back(make_pair(1,++cnt));
for(int i=1;i<=k;i++) {
cin>>a>>b>>c>>d>>h;
E1[a].push_back(make_pair(b,++cnt));
F[cnt]=make_pair(cnt+1,h);
E1[c].push_back(make_pair(d,++cnt));
}
E1[n].push_back(make_pair(m,++cnt));
for(int i=1;i<=cnt;i++) dp[i]=1e18;
dp[1]=0;
for(int i=1;i<=n;i++) {
sort(E1[i].begin(),E1[i].end());
int len=E1[i].size();
for(int j=1;j<len;j++)
dp[E1[i][j].se]=min(dp[E1[i][j].se],dp[E1[i][j-1].se]+x[i]*(E1[i][j].fi-E1[i][j-1].fi));
for(int j=len-2;j>=0;j--)
dp[E1[i][j].se]=min(dp[E1[i][j].se],dp[E1[i][j+1].se]+x[i]*(E1[i][j+1].fi-E1[i][j].fi));
for(int j=0;j<len;j++)
if(dp[E1[i][j].se]!=1e18&&F[E1[i][j].se].fi)
dp[F[E1[i][j].se].fi]=min(dp[F[E1[i][j].se].fi],dp[E1[i][j].se]-F[E1[i][j].se].se);
}
if(dp[cnt]<1e18) cout<<dp[cnt]<<endl;
else cout<<"NO ESCAPE"<<endl;
}
return 0;
}
F. Not Splitting
简要/形式化题意
给定一个 \(k \times k\) 的网格(\(k\) 为偶数),网格上有若干张 \(1 \times 2\) 的多米诺骨牌。选取若干条格边组成一条连续的线,将网格分成两个全等的部分。问最少需要拿走几张骨牌,才能使每张骨牌所占的两个格子均属于其中一个部分。
题解
看似无从下手,但可以发现如果从整个格子最中间的格点(注意,是格点),向相对的两边引两条关于该格点中心对称的两条线,就能将网格分成两个全等的部分。当然这个方法是充分且必要的,感性理解一下,本人不咋会证明这个抽象的东西。
总而言之,对与 \((i,j)\) 右下角的格点,若在分割线上,则其相对于最中间格点的中心对称点,\((k-i,k-j)\) 右下角的格点也必然在分割线上。
回到原问题,最少需要拿走几张骨牌,本质上就是在问,最少会穿过几张骨牌。从图的角度看待,\((k+1)^2\) 个格点,建立一个网格图,对于一张骨牌 \((r_1,c_1)\),\((r_2,c_2)\)(它所占的两个格子)。若 \(r_1=r_2\),则 \((r_1-1,c_1)\) 右下角的格点与 \((r_1,c_1)\) 右下角的格点之间的边权加 \(1\)。若 \(c_1=c_2\),则 \((r_1,c_1-1)\) 右下角的格点与 \((r_1,c_1)\) 右下角的格点之间的边权加 \(1\)。
至此,原问题被转化为,求一条经过中心格点且贯穿网格图(即 \((\dfrac{k}{2},\dfrac{k}{2})\) 右下角的格点)的最短路径,并且该路径满足最开始提到的那个结论。我们可以以中心格点为原点,利用 \(\text{Dijkstra}\) 算法同时向两边求最短路即可。时间复杂度为 \(O(k^2 \log k^2)\)。
为了方便实现,\((i,j)\) 右下角的格点哈希成 \(i \times (k+1)+j+1\) 存储。本质上把网格图的格边去掉,剩下的按序编号,简化建图。
AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=510;
const int M=1e6+10;
int t,k,n,r1,c1,r2,c2;
int vis[M],d[M];
map<pair<int,int>,int>Map;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--) {
priority_queue<pair<int,int> >q;
cin>>n>>k;
for(int i=1;i<=(k+1)*(k+1);i++) d[i]=1e18;
for(int i=1;i<=(k+1)*(k+1);i++) vis[i]=0;
Map.clear();
for(int i=1;i<=n;i++) {
cin>>r1>>c1>>r2>>c2;
if(r1>r2) swap(r1,r2);
if(c1>c2) swap(c1,c2);
if(r1==r2) {
Map[make_pair((r1-1)*(k+1)+c2,r1*(k+1)+c2)]++;
Map[make_pair(r1*(k+1)+c2,(r1-1)*(k+1)+c2)]++;
}
if(c1==c2) {
Map[make_pair(r1*(k+1)+c1,r1*(k+1)+c1+1)]++;
Map[make_pair(r1*(k+1)+c1+1,r1*(k+1)+c1)]++;
}
}
d[k*k/2+k+1]=0;
q.push(make_pair(0,k*k/2+k+1));
while(!q.empty()) {
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
vis[k*k+2*k+2-x]=1;
if(x<=k+1||x>k*(k+1)||x%(k+1)==1||x%(k+1)==0) {
cout<<n-d[x]<<endl;
break;
}
int y,z;
y=x-1,z=Map[make_pair(x,y)]+Map[make_pair(k*k+2*k+2-x,k*k+2*k+2-y)];
if(d[y]>d[x]+z) {
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
y=x+1,z=Map[make_pair(x,y)]+Map[make_pair(k*k+2*k+2-x,k*k+2*k+2-y)];
if(d[y]>d[x]+z) {
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
y=x+k+1,z=Map[make_pair(x,y)]+Map[make_pair(k*k+2*k+2-x,k*k+2*k+2-y)];
if(d[y]>d[x]+z) {
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
y=x-k-1,z=Map[make_pair(x,y)]+Map[make_pair(k*k+2*k+2-x,k*k+2*k+2-y)];
if(d[y]>d[x]+z) {
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
return 0;
}
考后反思
首先后两题没有充足的时间思考,毕竟只留了 \(16\) \(\text{min}\)。仔细分析一下时间布局,发现耗时最多的是 \(\text{B}\) 题,不过无论是哪题,耗时的原因的都是绕弯子没绕回来。想了一大堆奇葩做法结果没有果断舍弃掉。以后要看清数据范围,和优化的瓶颈选择做法。(比如 \(\text{C}\) 题,\(\text{DP}\) 的做法在转移上显然没有优化的余地,应该果断放弃,\(\text{B}\) 题,数据范围和题目描述并没有要求对于每一个 \(k\) 独立 \(O(1)\) 算出来,及时从数学式推导中跳出来),预估在这场比赛中可以省去半小时左右的时间。
补题的时候发现,这场比赛有很多是结论题,比如 \(\text{F}\) 题,只要结论一出来,正解基本上就是呼之欲出了。但是严格证明过程却远远难于题目本身。因此,造数据找规律是极为重要的。(\(\text{F}\) 题可以多画几条分割线,找一找公共格点,然后结论就出来了)。
总而言之就是:优化瓶颈和数据范围选算法;手造大小数据找规律猜结论。
结尾
考的还行~。

浙公网安备 33010602011771号