Codeforces Round 919 (Div. 2) 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17963255 ,转载请注明出处。
B 题没看数据范围,以为 可以为负数;DE 题都没有清空数组,浪费了很多时间。最后在结束前 10s 调过了 F1,但网页卡死了没交上去(大悲)。最终排名 203。
题目偏向于小清新风格,整体感觉良好,推荐。
2024/1/17update:期末考试期间借用机房电脑捉虫,更新 F2 代码。
传送门
A.Satisfying Constraints
题目大意
给出 条对整数 的限制,有以下三种格式:
-
。
-
。
-
。
保证至少有一条限制 1,一条限制 2,且不存在两条限制完全相同。
求满足条件的 的数量。
多组测试,。
思路
仅考虑限制 1 和限制 2,我们用两个指针 维护 的合法区间。
接下来每出现一个 在 内的限制 3,则答案减 。
代码实现
注意答案对 取最大值。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m;
ll a[N],b[N];
void mian(){
ll ans=0,l=1,r=1e9;
scanf("%lld",&n);
For(i,1,n){
scanf("%lld%lld",&a[i],&b[i]);
if(a[i]==1)l=max(l,b[i]);
if(a[i]==2)r=min(r,b[i]);
}
ans=r-l+1;
For(i,1,n){
if(a[i]==3&&l<=b[i]&&b[i]<=r)--ans;
}
printf("%lld\n",max(0ll,ans));
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
B.Summation Game
题目大意
Alice 和 Bob 玩游戏。有一个长度为 的正整数序列 。
首先,Alice 选择至多 个数,将它们删除。
然后,Bob 选择至多 个数,将它们变为它们的相反数。
接下来游戏结束,分数为最终序列的和。Alice 的目标是使其最大,而 Bob 的目标是使其最小。求最终分数。
多组测试,。
思路
将数组从小到大排序。
发现 Bob 一定会选择序列的一段后缀。所以为了“降低损失”,Alice 也会选择删去序列的一段后缀。
考虑 Alice 多删一个数的影响。
仔细思考发现当删的位置 时,影响为 ;当 时,影响为 。
那么我们定义一个位置的权值 ,并将 的后缀和记作 。
不难想到, 最大值所在位置即为 Alice 删去的后缀开始位置。
直接计算答案即可。
代码实现
Alice 是有删数限制的,所以只能从 枚举到 。
注意 Alice 可以不删数。
注意 的初始化。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
void mian(){
ll ans=0;
scanf("%lld%lld%lld",&n,&m,&k);
For(i,1,n){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
b[n+1]=0;
Rep(i,n,n-m+1){//计算s[i]
if(i>k)b[i]=b[i+1]+a[i]-2*a[i-k];
else b[i]=b[i+1]+a[i];
}
ll r=n+1;//n+1表示Alice不删数的情况
For(i,n-m+1,n){//找最大值
if(b[i]>b[r])r=i;
}
--r;//a[r]是被删掉了的
Rep(i,r,max(1ll,r-k+1))a[i]=-a[i];//Bob操作
For(i,1,r)ans+=a[i];
printf("%lld\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
C.Grouping Increases
题目大意
给定一个长度为 的序列 ,你需要找到满足以下条件的 的个数:
-
是 的因数。
-
能够找到一个大于 的整数 ,令 ,满足 完全相同。
两个长度为 的序列 完全相同当且仅当 。
多组测试,。
思路
由于数字 的因子个数是 级别的,所以我们可以找到所有的 ,然后快速判断是否合法。
发现数 满足取余一个数 后值相同,那么 。
其实这个结论可以拓展到多个数。
所以我们可以对于每个 ,暴力计算 ,然后判断是否满足 不为 的条件即可。
复杂度 。
代码实现
注意 时也是合法的,表示任意一个 都满足条件。
代码中将计算 一步写作 ,效果相同。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
void mian(){
ll ans=0;
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
}
//记录因子
vector<ll>t;
for(ll i=1;i*i<=n;++i){
if(n%i==0){
t.pb(i);
if(i!=n/i)t.pb(n/i);
}
}
//判断是否满足条件
for(ll x:t){
ll g=0;
For(i,x+1,n){
g=__gcd(g,abs(a[i]-a[i-x]));
}
if(g!=1)++ans;
}
printf("%lld\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
D.Array Repetition
题目大意
有一个数列 ,初始为空。有两种操作:
-
在序列末尾插入一个数 。
-
将整个数列复制,并在数列后方粘贴 次。
给定 个操作。所有操作进行完之后,进行 次询问,每次询问序列中下标为 的位置上的值。
多组测试,。操作 1 中 ,操作 2 中 ,询问中 。
思路
发现操作 2 至少会将序列长度增加一倍,而查询范围最大只有 ,所以我们最多需要处理 次操作 2。
我们在处理操作时维护一下序列长度,当长度超过 时不再处理操作。
那么,我们采用一个链式结构。每个节点上存储三个信息:上次操作 2 时的序列长度 、上次操作 2 后的序列长度 、操作 1 插入的数构成的序列 。
每当一个操作 1 出现时,我们向当前节点的 中插入数即可。
每当一个操作 2 出现时,我们新建一个节点,记录 与 的值。
对于一个查询,我们从最后一个节点开始。可以根据 判断此下标是否在当前节点的 上。如果不在,递归向前一个节点查询,否则直接返回。
显然最多只会有 个节点,而我们每次查询时最多遍历每个节点各一次。复杂度 。
代码实现
序列长度可能爆 long long
,可以用除法防溢出或开 __int128
。
记得清空。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
struct node{
__int128 m;
ll tmp;
vector<ll>t;
}tr[N];
ll n,k,q;
ll a[N],b[N];
void clear(ll x){//清空节点
tr[x].m=tr[x].tmp=0;
tr[x].t.clear();
}
void mian(){
k=1;
clear(k);
__int128 m=0;//记录当前区间长度
scanf("%lld%lld",&n,&q);
For(i,1,n){
ll x,y;
scanf("%lld%lld",&x,&y);
if(m>=1e18)continue;
if(x==1){
tr[k].t.pb(y);
++m;
}else{
clear(++k);
tr[k].tmp=m;
m*=y+1;
tr[k].m=m;
}
}
For(i,1,q){
ll x;
scanf("%lld",&x);
ll pos=k,ans=-1;
while(pos){
if(x>tr[pos].m){
ans=tr[pos].t[x-tr[pos].m-1];
break;
}else{
x=(x-1)%tr[pos].tmp+1;
--pos;
}
}
printf("%lld ",ans);
}
printf("\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
E.Counting Binary Strings
题目大意
一个 01 串是好的,当且仅当其中只有一个 。
一个 01 串的分数定义为它的所有连续子串中,好的 01 串个数。
给定 和 ,求出分数为 ,连续子串中好的 01 串最大长度不超过 的 01 串个数。
答案对 取模。
多组测试,。
思路
思考如何快速计算一个 01 串的分数。
由于一个好的 01 串仅有一个 ,考虑截取 00...0100...0
这样一个结构进行研究。
令左右分别有 个 ,那么这个结构的分数为 。
由于填入一个 的贡献仅与当前结尾的连续的 的数量有关,考虑 dp。
设 表示分数为 ,结尾连续的 的个数为 的 01 串个数。
不过这样定义不太方便,所以我们令 表示结尾连续的 的个数 。
枚举填入 个 ,注意 。转移式为 。
但是乍一看,你这转移是 的,肯定不能过。
仔细观察我们的转移过程。我们从 转移到 ,其中 是一个乘积的形式。
我们将 的情况去掉,发现实际转移次数很少。
转移次数的具体计算比较复杂,所以在正式比赛时建议大家造数据实测来判断。
代码实现
记得清空。
#pragma GCC optimize("Ofast")//codeforces允许使用pragma语句,看上去比较玄学的题目建议开上
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=2500+10;
using namespace std;
const ll p=998244353;
ll n,m,k;
ll a[N],b[N];
ll dp[N][N];
void mian(){
ll ans=0;
scanf("%lld%lld",&n,&k);
For(i,1,k){
dp[0][i]=1;
}
For(i,0,n-1){
For(j,1,k){
For(t,1,k+1-j){
if(i+j*t>n)break;
dp[i+j*t][t]=(dp[i+j*t][t]+dp[i][j])%p;
}
dp[i][j]=0;//清空
}
}
For(i,1,k)ans=(ans+dp[n][i])%p,dp[n][i]=0;
printf("%lld\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
F1.Smooth Sailing (Easy Version)
题目大意
与 F2 不同之处已标红。
有一片 的网格,上面有三种格子:障碍、陷阱与地面。保证障碍四连通且不与网格边缘接触,至少有一个陷阱格子。
你可以通过四连通方式移动。你不能经过障碍。你需要走一条回路(只要起点和终点相同即可,可以重复经过格子),将障碍包围在回路之内(从障碍的任意一点出发,八连通不经过回路上的格子无法到达网格边缘)。
路线的安全值为你走过的格子中到陷阱的最小曼哈顿距离。计算曼哈顿距离时不受障碍限制。
现在给出网格的情况,有 次询问,每次给出一个坐标 ,求起点为 的符合条件的回路的最大安全值。保证 是地面。
。
思路
询问次数可以看做 ,也就是说我们只需要找到一种方式去计算最大安全值。
最小值最大,考虑二分答案。
首先,通过多源 bfs,预处理出每个点到最近的陷阱的距离 ,特别的,障碍的 值为 -1,表示不能经过。
然后二分答案,仅能走 不小于当前二分值的格子。可以通过 bfs 将所有与起点四连通且满足条件的格子全部标记出来。
由于障碍是四连通的,所以我们只需要选择一个点去判断八连通能否到达网格边缘。这同样通过 bfs 实现。
复杂度 。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define Yes printf("Yes\n")
#define No printf("No\n")
#define pb push_back
const ll N=1e6+10;
using namespace std;
//const ll p=1e9+7;
const ll p=998244353;
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}
ll n,m,k,q;
vector<ll>a[N],vis[N],dis[N];
ll nt[4][2]={0,1,1,0,0,-1,-1,0};//四连通
ll nt2[8][2]={0,1,1,0,0,-1,-1,0,1,1,-1,-1,1,-1,-1,1};//八连通
ll tx,ty;
bool check(ll d,ll x,ll y){
if(d>dis[x][y])return false;
For(i,1,n)For(j,1,m)vis[i][j]=0;//清空
//bfs标记所有能走到的满足条件的格子
queue<ll>qx,qy;
qx.push(x),qy.push(y);
vis[x][y]=1;
while(qx.size()){
ll x=qx.front(),y=qy.front();
qx.pop(),qy.pop();
For(i,0,3){
ll xx=x+nt[i][0],yy=y+nt[i][1];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(vis[xx][yy]||dis[xx][yy]<d)continue;
vis[xx][yy]=1;
qx.push(xx),qy.push(yy);
}
}
//从障碍格子八连通出发,尝试不经过已标记格子到达网格边缘
qx.push(tx),qy.push(ty);
vis[x][y]=1;
while(qx.size()){
ll x=qx.front(),y=qy.front();
qx.pop(),qy.pop();
For(i,0,7){
ll xx=x+nt2[i][0],yy=y+nt2[i][1];
if(xx<1||xx>n||yy<1||yy>m)return false;//到达网格边缘,说明回路未包含障碍
if(vis[xx][yy])continue;
vis[xx][yy]=1;
qx.push(xx),qy.push(yy);
}
}
return true;
}
void solve(ll x,ll y){
ll l=0,r=n+m,res=-1;
while(l<=r){//二分答案
ll mid=(l+r)>>1;
if(check(mid,x,y))l=mid+1,res=mid;
else r=mid-1;
}
printf("%lld\n",res);
}
void mian(){
ll ans=0;
scanf("%lld%lld%lld",&n,&m,&q);
queue<ll>qx,qy,qd;
For(i,1,n){
a[i].resize(m+1);
vis[i].resize(m+1);
dis[i].resize(m+1);
while(getchar()!='\n');
For(j,1,m){
char ch=getchar();
if(ch=='#')a[i][j]=1,tx=i,ty=j;//随便记录一个障碍格子
if(ch=='v')qx.push(i),qy.push(j),qd.push(0),vis[i][j]=1;
}
}
//多源bfs,计算dis
while(qx.size()){
ll x=qx.front(),y=qy.front(),d=qd.front();
qx.pop(),qy.pop(),qd.pop();
dis[x][y]=d;
For(i,0,3){
ll xx=x+nt[i][0],yy=y+nt[i][1];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(vis[xx][yy])continue;
vis[xx][yy]=1;
qx.push(xx),qy.push(yy),qd.push(d+1);
}
}
For(i,1,n)For(j,1,m)if(a[i][j])dis[i][j]=-1;//将障碍格子的dis标记为-1
For(i,1,q){
ll x,y;
scanf("%lld%lld",&x,&y);
solve(x,y);
}
}
int main(){
int T=1;
while(T--)mian();
return 0;
}
F2.Smooth Sailing (Hard Version)
题目大意
与 F1 不同之处已标红。
有一片 的网格,上面有三种格子:障碍、陷阱与地面。保证障碍四连通且不与网格边缘接触,至少有一个陷阱格子。
你可以通过四连通方式移动。你不能经过障碍。你需要走一条回路(只要起点和终点相同即可,可以重复经过格子),将障碍包围在回路之内(从障碍的任意一点出发,八连通不经过回路上的格子无法到达网格边缘)。
路线的安全值为你走过的格子中到陷阱的最小曼哈顿距离。计算曼哈顿距离时不受障碍限制。
现在给出网格的情况,有 次询问,每次给出一个坐标 ,求起点为 的符合条件的回路的最大安全值。保证 是地面。
。
思路
询问次数达到了 ,显然是想让我们把每个点的答案预处理出来。
那么我们可以省去二分答案,直接按 从大到小枚举每个格子,依次标记为可经过的。
但是我们如何判断回路是否包含障碍呢?
如果有计算几何基础在这一步会容易很多。
我们从障碍处向左画一条水平的射线,如果回路经过了奇数次射线,那么回路包含障碍。
想到这里,自然地考虑使用并查集维护连通性。
我们将一个点 拆成两个,代表经过了奇数或偶数次射线。当两个点在并查集中连通时,说明有一条回路满足条件。由于我们按 从大到小的顺序枚举格子,所以直接记录答案即可。
复杂度 。
代码实现
注意细节。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll n,m,k,q;
vector<ll>a[N],vis[N],dis[N],ans[N];
ll nt[4][2]={0,1,1,0,0,-1,-1,0};//四连通
ll nt2[8][2]={0,1,1,0,0,-1,-1,0,1,1,-1,-1,1,-1,-1,1};//八连通
vector<pair<ll,ll>>t[N];//记录每个dis对应的格子
ll tx,ty;
ll fa[N];
vector<ll>s[N];
ll find(ll x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
void merge(ll x,ll y,ll res){
x=find(x),y=find(y);
if(x==y)return;
if(s[x].size()<s[y].size())swap(x,y);
for(ll i:s[y]){
ll id=(i-1)%(n*m)+1;
ll j;//i的对应点的编号
if(i>n*m)j=i-n*m;
else j=i+n*m;
if(find(j)==x){
ans[(id-1)/m+1][(id-1)%m+1]=res;
}
s[x].pb(i);
}
s[y].clear();
fa[y]=x;
}
bool check(ll x1,ll y1,ll x2,ll y2){//判断从(x1,x2)到(y1,y2)是否经过了水平射线
//这里我们把水平射线画在(tx,0)到(tx,ty)这些格子的上边缘,方便判断
if(x1==x2||y1>=ty)return false;
if(x1>x2)swap(x1,x2);
return x1==tx-1&&x2==tx;
}
#define id(x,y) ((x-1)*m+y)
void init(){
For(i,1,2*n*m)fa[i]=i,s[i].pb(i);
Rep(i,n+m,0){
for(auto j:t[i]){
ll x=j.first,y=j.second;
For(k,0,3){
ll xx=x+nt[k][0],yy=y+nt[k][1];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(dis[xx][yy]<i)continue;
if(check(x,y,xx,yy)){
merge(id(x,y),id(xx,yy)+n*m,i);
merge(id(x,y)+n*m,id(xx,yy),i);
}else{
merge(id(x,y),id(xx,yy),i);
merge(id(x,y)+n*m,id(xx,yy)+n*m,i);
}
}
}
}
}
void mian(){
scanf("%lld%lld%lld",&n,&m,&q);
queue<ll>qx,qy,qd;
For(i,1,n){
a[i].resize(m+1);
vis[i].resize(m+1);
dis[i].resize(m+1);
ans[i].resize(m+1);
while(getchar()!='\n');
For(j,1,m){
char ch=getchar();
if(ch=='#')a[i][j]=1,tx=i,ty=j;//随便记录一个障碍格子
if(ch=='v')qx.push(i),qy.push(j),qd.push(0),vis[i][j]=1;
}
}
//多源bfs,计算dis
while(qx.size()){
ll x=qx.front(),y=qy.front(),d=qd.front();
qx.pop(),qy.pop(),qd.pop();
dis[x][y]=d;
For(i,0,3){
ll xx=x+nt[i][0],yy=y+nt[i][1];
if(xx<1||xx>n||yy<1||yy>m)continue;
if(vis[xx][yy])continue;
vis[xx][yy]=1;
qx.push(xx),qy.push(yy),qd.push(d+1);
}
}
For(i,1,n){
For(j,1,m){
if(a[i][j])dis[i][j]=-1;//将障碍格子的dis标记为-1
else t[dis[i][j]].pb({i,j});
}
}
init();//预处理
For(i,1,q){
ll x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",ans[x][y]);
}
}
int main(){
int T=1;
while(T--)mian();
return 0;
}
尾声
如果有什么问题,可以直接评论!
熬夜写到 3:40,给个赞不过分吧 QAQ
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战