1938.2024 ICPC Asia Pacific Championship - sol
20240302-20240308
2024 ICPC Asia Pacific Championship - Online Mirror
和 Mea,Hanghang 组队一起打,只做了 F,三个人不会 G,我又被简单的 C 搏杀。。。
现阶段没有补完,待更新。进度:11/13
D 和 M 是多项式题目,一道 FFT,一道 NTT,由于笔者太菜不会多项式,所以这两道没有补。
L 是线性基题,笔者不会证明,结论来自于找规律。
部分题目的做法和题解不同,有些是自己想出来的做法,有些则是看不懂题解看选手代码得到的做法。
笔者深知看机翻官方题解的痛苦,所以还是来写一篇题解(英语词汇量不够是这样的)。
或许我写的也像机翻官方题解。
每道题的知识点:
A - 奇妙构造 B - 图论,平面图 C - 二进制相关,找规律 D - 多项式,FFT
E - 简单思维题 F - 思维题,素数相关 G - 鸽巢原理,思维题 H - 签到题,贪心
I - 计算几何(凸包) J - 图论(最短路) K - DS,数论 L - 线性代数
M - 多项式,生成函数,NTT
A. Antiparticle Antiphysics
给你两个仅有字母
A,P
组成的字符串和两个正整数 ,请你 构造 一组 合法 操作序列将 变成 ,有以下四种操作:
+P i
:从左到右第个字符 P
变成APA
,即在P
两边各插入一个A
。(如果第个字符不为 P
,则操作不合法)+A i
:从左到右第个字符 A
变成PAP
,即在A
两边各插入一个P
。(如果第个字符不为 A
,则操作不合法)-P i
:从第个字符起删除连续 个字母 P
,需要保证第个字符都是 P
,否则操作不合法。-A i
:从第个字符起删除连续 个字母 A
,需要保证第个字符都是 A
,否则操作不合法。设你构造出的操作序列的操作个数为
,你需要保证 ,如果无解则输出 -1
;反之输出行,第一行为一个正整数 ,接下来 行依次输出 个操作,每个操作形如上面的四种。
。
好题!但是一来就感觉不太可做,或许结合代码看会比较简单。
对于这种构造题,我们首先还是要去手玩样例,去分析一种还原的策略的。
这里我们记 A
,P
同理。
我们通过手玩可以发现以下简单的规律(A,P
可以互换,设空串为
而还容易发现一些无解的情况。因为我们每次操作只会 A
字符个数差是奇数,那么是无解的;对于 P
同理。
想到这里,似乎有一些感觉了,对于第四条转化,
我们发现我们可以凭空造出 A
来放在左右,这是非常好的性质,也为我们后面的推导埋下了伏笔。
而由于我们可以把
除去这种特殊情况之外,其实是简单的,因为总是可以一直用操作
在这里插叙一段元操作的代码,
void add(int x){
int len=strlen(S);
if(S[x]=='A'){
ans.pb({"+A",x+1});
while(len>x) S[len+2]=S[len],--len;
S[x]=S[x+2]='P',S[x+1]='A';
}else{
ans.pb({"+P",x+1});
while(len>x) S[len+2]=S[len],--len;
S[x]=S[x+2]='A',S[x+1]='P';
}
}
void sub(int x){
int len=strlen(S);
if(S[x]=='A'){
ans.pb({"-A",x+1});
while(x+a<=len) S[x]=S[x+a],++x;
}else{
ans.pb({"-P",x+1});
while(x+p<=len) S[x]=S[x+p],++x;
}
}
于是我们考虑这样构造一种方案(以下的想法还仅限于 A,P
本质相同,是可以互换的):
我们希望把
以 S=AP,E=PPAP,a=5,p=4
为例,代码中 A,P
的个数,操作次数的计算是粗略的。
-
将
操作成 的前缀:当前匹配到 位(下标从 开始)。【操作次数: 】- 发现
S[i]!=E[i]
,那么我们考虑对当前这一位 进行一次操作,那么这一位就匹配了, 。 - 如果
的这一位没有了,例如S=PP
,我们考虑对 操作两次,重新回到 位进行匹配(这里 ): 。
for(i=0;i<m;i++) if(S[i]!=E[i]){ if(S[i]!=0) add(i); else add(i-1),add(i-1),--i; }
现在任务就变成了把后面的一段消除。
- 发现
-
若后面只有一种字符,我们对
位置进行操作,使得两种字符都出现,因为接下来的操作两个字符相互依赖。【操作次数: 】 。if(!s1||!s2) add(m),(!s1)?s1=2:s2=2;
-
把字符
P
补齐,使得P
可以被删完,即P
的个数是 的倍数。(一定会存在合法的方案,之前已经把不合法的方案判了)【操作次数: 】 ,接下来的推导省略前缀PPAP
。while(s2%p) add(i),++i,s2+=2;//i 是第一个 A 的下标
-
将所有的
A
移动到所有P
后面,且不改变P
个数:对于每一个A
,我们找到它所在的连续段,对于这个连续段后面的那个P
操作,使得刚好可以用多次删除操作删完这个P
前面的A
。【操作次数: ?】 。for(i=m;S[i];i++) if(S[i]=='A'){ j=i; while(S[j]=='A') ++j; if(S[j]==0) break; while((j-i)%a) add(j),++j; while(j-i>0) sub(i),j-=a; }
-
试图补全后面的
A
,使得个数能被 整除(从而方便后面的操作):由于 ,每次我们会多 个 ,所以一定可以完成。【操作次数: 】 , 是A
开始的位置。while((j-i)%4){ for(int k=0;k<a;k++) add(i-1+k); sub(i-1),j+=a; }
-
用一个
P
把A
串劈成两段,不改变P,A
的个数这样就可以删除了:每次可以将P
右移两个,所以上一步要变成 的倍数。【操作次数: ?】 。for(int t=0;t<(j-i)/4;t++){ for(int k=0;k<p-1;k++) add(i+2*t+k); sub(i+2*t-1),add(i+2*t+p-1),sub(i+2*t); } j=i-1+(j-i)/2,--i;//i 是 A 开始的位置,j 是中间 P 的位置
-
通过中间的那个
P
的操作,让两边都有 的倍数个A
,再将A
全部删除。【操作次数: 】 ,过程中P
的个数一直不变。while((j-i)%a) add(j),++j; while(j-i>=a) sub(j+1),sub(i),j-=a;
-
最后把剩下的
的倍数个P
全部删除即可。【操作次数: 】 。while(s2) sub(m),s2-=p;
这样我们就完成了一个非常好的构造方式!它可以满足任何非 A,P
交换进行操作)。
现在我们仔细分析一下,其实对于 A
变成
那么现在我们进行手玩第一步的前缀和操作和在 S=AAAAPA,E=APPPPP
是无解的),
可以发现,在构造完前缀之后,若有非 A,P
则无解,而构造过程中(假设第一个要 P
)一直不遇见 P ... A
时 A
就会一直构造产生 P
,
我们发现这个东西和 P ... A
的奇偶性是相关的。(可能这个地方比较感性,欢迎提出更理性的证明)
于是在这种情况下是无解的:
if(!(a%4)&&!(p%4)){
int cnt=0;
for(i=0;i<n;i++) for(j=i+1;j<n;j++) cnt+=(S[i]=='P'&&S[j]=='A');
for(i=0;i<m;i++) for(j=i+1;j<m;j++) cnt+=(E[i]=='P'&&E[j]=='A');
if((cnt+(s1-e1)/2+(s2-e2)/2)&1) return false;
}
感觉就非常正确,笔者找规律理解的。
发现判断完这些东西,其实我们的特殊情况也可以像之前那样处理了,于是这道题就做完了。
简单加一下操作次数,发现通过是极其轻松的,无论如何都不会过
代码大概写了
B. Attraction Score
给定二维平面上的
个点,第 个点的坐标为 。同时有 条边,第 条边从 连到 分数为 ,在平面图中用 的线段表示。 保证任意两条边(线段)不相交!!!
现在你需要从里面选出一个若干个点的子集
使得这个子集中的分数最大,一个子集的分数被定义为:
- 对于任意一对子集中的点对
如果图中存在这样一条 的线段,那么分数会加上这条边的分数 。 - 设子集中没有连边的点对
的个数为 ,那么分数减去 。 你需要找出所有选取方案中,得到分数的最大值,并输出这个分数。
。
图论好题。
首先,我们需要知道这样的一个结论。
由于是在一个平面图上,
画图感性理解一下,发现非常正确,并且构造出这个上限是简单的,你每次往一个三角型的里面加一个点,那么它的贡献就是
有了这个结论,我们发现当
而
所以对于
对于
而对于
现在分析清楚了,我们考虑如何去实现呢?
对每一个点枚举?但是如果一个点的度数很大怎么办呢?
这里我们想到从度数最小的点入手,而由于度数之和
我们每次把这个点提出来,对于这个点所连接的点统计答案,再将其删除,变成一个残图,这样就可以不重不漏地统计了。
只需要用一个 priority_queue 来维护。
而注意到我们是允许缺失一条边的,所以当枚举出的一个子集它一条边都不缺失时我们还要考虑是不是还可以加一个点(它有可能和当前枚举到的点不连边),
所以这里我们还需要用 map 维护一下有那些点和一个集合连了边,有一些细节需要特殊处理一下,具体可以看下面这段核心代码:
for(auto i:mp[x]) id[sz]=i.fi,w[sz++]=i.second;//mp 存邻接矩阵
for(int i=0;i<sz;i++) for(int j=0;j<sz;j++) if(mp[id[i]].count(id[j])) f[i][j]=mp[id[i]][id[j]];
for(int i=0;i<(1<<sz);i++){
int s=0,c=0;
for(int j=0;j<sz;j++) if(i>>j&1){
s+=w[j];
for(int k=j+1;k<sz;k++) if(i>>k&1) f[j][k]==-1?++c:s+=f[j][k];
}
s-=M*c*c;ans=max(ans,s);
if(i&&!c){//没有缺失边,还可以再加入一个点
vector<int> nw;int sum=0;nw.pb(x);
for(int j=0;j<sz;j++) if(i>>j&1) nw.pb(id[j]),sum+=w[j];
sort(nw.begin(),nw.end());//当前已经选的集合
for(int j=0;j<(int)nw.size();j++){
vector<int> cur=nw;cur.erase(cur.begin()+j);
if(C.count(cur)) ans=max(ans,s+C[cur]-M);
}
nw.erase(lower_bound(nw.begin(),nw.end(),x));
C[nw]=sum;//map 记录可选的点,可以不重不漏
}
}
这样就做完了,时间复杂度
C. Bit Counting Sequence
令
表示 的二进制表达中 的个数,现在给你一个长度为 的数组 ,希望能找到一个 满足 与 一一对应,如果存在,则输出最小的 ;反之输出 。
。
本人用了一种纯找规律的方法,并不太用到
考虑把
发现对于每
再把视线放大一点,把每四个看成一个数,取其第一个,发现数组也是
继续放大,每
我们得到什么规律了?
你把
那么我们只要不断找
直到我们最后剩下一个数,而找到这个数
这样我们不断迭代下去,用每一次的
场上没有把无解判完——相对失败。但是整体而言代码写起来还是非常有意思的。代码。
而队友则给出了另外的思路,简单来说就是根据两个相邻数的差距可以得到一些低位确定,这样似乎会没有那么多细节?代码。
E. Duplicates
给你一个
的矩阵,每个位置的值都在 范围内。 一个长度为
的序列是好的,当且仅当存在一个二元组 满足 。 每一次修改可以将矩阵中
行 列的数修改成 ,现在求将矩阵的每一行每一列都变成好的的最小操作次数,并输出这一操作序列。
。
感觉有点诈骗的。
首先,你修改一个点,它的贡献最多是
而改成的数细想一下也是非常好想的,因为由于一行/一列不是好的,所以
对于贡献两次的那些点我们把它改成任意一个不是它本身的数都是可以的。
而对于那些贡献为
这样就做完了,实现是简单的,主要是限制了值域
F. Forming Groups
有
个学生,编号为 ,每个学生有一个能力值 。编号为 的学生的顺序是不能改变的,而你可以把第 人插入 中的任意两个人中间。 现在你可以任意选择一共整数
, 是 的因子,把学生分成 组,具体分组方式类似于 的剩余系: 第
个在第 组;第 个在第 组; ;第 个在第 组;第 个在第 组; ;第 个在第 组。 每一组的能力值被表示为组内同学的能力值之和,现在你需要找到一个合法的
使得每组能力值的 (即 ) 最小,并输出这个值。
。
场上的唯一贡献,还有一发数组开小了/fn
首先考虑枚举每一个
如果单纯地是把每一个
我们尝试把整个操作看成交换两个数,每次把
而这种统计是简单的,因为每一次只会改变两组的能力值,所以我们用一个 set 或者 priority_queue 维护一下就可以了,
这样可以做到单次
但是这样交上去会 T,为什么?
容易想到是枚举的因数
稍微改一下程序自己找一点数据跑一下,发现是显然的,于是在场上你就可以写一发交了,你就可以过了。
可是为什么呢?
发现证明是简单的,我们以
分成
用类似的方式我们可以证明其他的分解,所以这样当
那么这道题我们就做完了,最多只会枚举
G. Personality Test
给你
个长度为 的字符串,每个字符串由 .
和大写英文字母组成,任意两个字符串的相似度被定义为使 .
的的个数,现在要求你找到一组相似度 至少 为 的字符串 。如果由多组满足,则输出 最小的;若相同的 有多个 满足条件,则输出 最大的。不存在输出 。
。
三个人都被诈骗哩。
首先,问题的方式就非常奇怪——输出一组?
不让你计数就挺奇怪的。
想一个最简单的暴力,枚举任意两个串,用
于是考虑优化,搞不好直接往 bitset 想了,于是就回不来了。
可是
发现如果当前枚举到的总的相似度是
于是用一个 vector 存一下在第
这样我们就做完了!想到 鸽巢原理 你就赢了,时间复杂度
H. Pho Restaurant
给定
个 串,一个操作可以让一个字符从一个字符串移到另一个字符串,现在问最小的操作次数使得每个串要么全是 要么全是 。
。
用最长的题意描述最简单的问题。
直接贪心就可以了,如果一个串的
全一全零的情况可能需要判一下,相信签到题大家都会,时间复杂度线性。代码。
I. Symmetric Boundary
给定
个平面直角坐标系上的点 ,现在需要你在这个平面上面找到一个中心对称的凸多边形,使得这 个点都在这个多边形的顶点或边缘上面。 如果存在输出凸多边形的最小面积;不存在输出
。
,输入按照逆时针顺序。
ACM 中的 Geo,没有太多思路。
由于我们找到的这个凸多边形是一个中心对称的图形,我们假设它的对称中心是
那么我们把每一个
于是判断就只需要看对称之后得到的
但是非常明显,这样的
首先考虑任意三个点
即对称中心在图中的红色区域:
进而我们会发现,我们取这两条红线的中点,并确定一条直线,
对于所有的这
为什么呢?(建议自己画图)
首先对于每一个封闭的区域,在里面选点是不会影响图形的相对顺序的,也就是凸包上面的顺序,
而由于每一条直线都是由一些终点所组成的,所以不会存在一种情况使得在里面是凸的在边缘就是凹的了。
且我们的每一条直线所决定的多边形的面积一定是单调的,这是可以简单拆式子证明的,
所以我们选的点一定是在顶点!
(感觉用到两个中点的连线是非常妙的,它不仅可以限制凹凸性和顺序,还可以满足面积的单调)
所以我们只需要把这
再每次用
这样的总时间复杂度是
J. There and Back Again
给定
个点 条边的无向图,边有边权,你需要从 再走回来,使得两次路径的边的集合不同,并且边权之和最小,求这个最小值。
。
并不要求不能走重复的边,只要有一条边只在一个路径中走过就行。
看错题然后一眼网络流。
由于只需要一条边不同,所以我们找到最短路之后直接枚举一下那条边不同就可以了。
具体来说,两次 dijsktra 分别以
于是对于第二次走,枚举每一条边,如果这条边没有在最短路中出现,那么我们就算上
找到最小的方案,直接加上最短路长度输出就可以了。
时间复杂度 (这个程序有 bug,是数据水了?)。代码(这个才不会被 Hack!)。
K. Tree Quiz
给定一棵
个点的有根树,存在这样一个长度为 的数组 ,它的构造方式是:
for x=1 -> n for y=1 -> n a[++cnt]=(x-1)*n*n+(LCA(x,y)-1)*n+(y-1) sort(a+1,a+cnt+1);
注意最后要让
数组从小到大排序。 给出
个询问,每次输入一个整数 ,询问 的值。
。
给出一个单 log 离线做法!!! 下面是第一次写的题解,写着写着发现可以单 log。。。
可能不是题解的做法(没看),给出一个
没有强制在线,一来就没往在线去想。
分析式子,由于
先让
对于
那么我们可以用
for(int i=1;i<=m;i++) cin>>Q[i].v,Q[i].x=(Q[i].v-1)/n+1,Q[i].v-=1ll*n*(Q[i].x-1),H[Q[i].x].pb(i);
然后我们考虑找 LCA,发现这是可以在一次 dfs 中搞定的。
现在我们依次 dfs 到
找排名这件事很适合让二分来做,所以我们直接在
void dfs2(int u,int fa){
upd(fa,-sz[u]),upd(u,sz[u]);
for(auto i:H[u]){
int l=1,r=n;
while(l<r) qry(mid)>=Q[i].v?r=mid:l=mid+1;
Q[i].z=l,Q[i].v-=qry(l-1),F[l].pb({i,son[l]});
}
for(auto v:G[u]) son[u]=v,dfs2(v,u);
upd(fa,sz[u]),upd(u,-sz[u]);
}
找到了
笔者尝试用了许多 STL 实现,但发现都不行,于是索性直接用线段树合并完成。
我们同样是二分,每次查个数就可以了,会有一些细节,还需要开两个线段树。
这样的时间复杂度是
现在来讲那个单 log 做法! 笔者太菜,上面的题解写完了才发现。
不是,我都想到那儿了单 log 不就出了?
聪明的你已经发现,不是把两次二分都变成 线段树二分 就可以完成了,而且会发现其实两次的 dfs 可以合并。
于是我们就得到了离线
再在这里提供一个较短的,
L. XOR Operations
给定
和 个数 ,你有一个长度为 的数组 ,初始所有值都是 。 对于一次操作,可以选择两个整数
,进行 和 的赋值。 现在你可以进行若干次操作,问最后可以得到多少种不同的
数组,方案数 。
。
一篇非常水的题解,三个字——找规律。笔者太菜,看不懂题解。
首先,与异或相关我们就可以往线性基方面想,于是你先写一个暴力出来。
发现答案一定是
但是发现
这样经过大量的尝试与发现,就可以通过这一道题了!!!代码。
Conclusion
- 构造题一定要手玩样例,经过大量的模拟之后得到一种可行的操作顺序。(A)
个点的平面图上可构成的不相交的边最多 条,枚举图中点集可以考虑按照度数从小到大枚举。(B)- 每个地方都需要判断一下是否存在不合法的情况啊,找规律的题目一定要仔细!(C)
- 与值相关的问题注意值域范围,有些值域可以构成非常简单的做法啊。(E)
- 有关计数/贪心的问题(奇怪的数据范围)一定要考虑能否用 鸽巢原理 得到一些结论。(B,G)
- 计算几何的很多题目都是取枚举答案,而很多枚举是无用的,所以我们考虑去找到那些有用的点。(I)
- 异或相关一般与线性基相关,于是可以找规律。(L)
终,于,写,完,了,工作量有点大的。
本文作者:H_W_Y
本文链接:https://www.cnblogs.com/H-W-Y/p/18061837/cf1938
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步