Codeforces Round #704 (Div. 2) 题解
本场链接:Codeforces Round #704 (Div. 2)
闲话
大Fst场,排名从781->449,给我看傻了都.D题非常依赖特判,感觉我也不大能讲清楚所有情况.E的话好像也比较码农,正在写,可能晚上做出来了就更新,没做出来的话明天更.
A. Three swimmers
有三个游泳的,三个人分别会在\(0,a,2a,3a,...\),\(0,b,2b,3b,...,\)以及\(0,c,2c,3c\)的时刻位于游泳场的最左端.现在你站在最左端,时刻为\(p\),问最少多久之后,会有一个运动员到最左端来.
思路
答案比较明显是求\(a - p \% a\)的最小值,由于取模的特殊性,当\(p\%a==0\)的时候,答案事实上是\(0\)需要特判.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
int T;scanf("%d",&T);
while(T--)
{
ll p,a,b,c;scanf("%lld%lld%lld%lld",&p,&a,&b,&c);
ll res = a - p % a;
res = min({res,b - p % b});
res = min({res,c - p % c});
if(p % a == 0 || p % b == 0 || p % c == 0) res = 0;
printf("%lld\n",res);
}
return 0;
}
B. Card Deck
一个排列长度为\(n\).每次你要选取末尾的一部分元素,在不改变内部顺序的前提下整体挪到另外一列中,若干次操作之后整个数组都被挪动完毕,对新的长度不变的数组求权:\(\sum\limits_{i=1}^n n^{n-i}p_i\).使此值最大.
思路
观察样例可以发现,每次挪动其实就是把末尾一段抠出去放在另外一列屁股后面接着.而且样例有个很大的规律就是每次尽可能让当前数列里面最大值站在最前面,简单分析一下可以明确这个结论的正确性,因为\(n^{n-i}\)很大,让\(p_i\)即是变大一点点都会增大很多,也就是让最大的\(p_i\)站在最前面一定是最优的.贪心分配每一段的位置即可.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
const int N = 1e5+7;
int a[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]);
int maxv = n;
set<int> st;
forn(i,1,n) st.insert(a[i]);
vector<int> cur;
forr(i,1,n)
{
st.erase(a[i]);
cur.push_back(a[i]);
if(a[i] == maxv)
{
reverse(cur.begin(),cur.end());
for(auto& v : cur) printf("%d ",v);
cur.clear();
if(!st.empty()) maxv = *(--st.end());
}
}
puts("");
}
return 0;
}
C. Maximum width
给定两个字符串S
和T
.要求在S
里面找一个子序列长度为\(|T|\)且顺次恰好组成T
.记他们的下标分别为\(p_1,p_2...p_m\),求\(\max\{p_{i+1}-p_i\}\)的最大值.
思路
就是要找一个子序列和T
相等,完了让其中两个相邻元素取的间隔最大.那我们不妨枚举一下具体哪个元素间隔需要取最大:枚举\(i\),要求\(i,i+1\)之间下标之差的最大值.这一步可以做一个预处理:\(f[i]\)表示前缀\(T[1-i]\)全部选择完毕的前提下,\(T[i]\)最靠左的选择下标是多少,同理可以对称的求一个\(g[i]\)表示后缀选择的最靠右的下标,那么答案统计就是\(\max\{g[i + 1] - f[i]\}\).这里有个小问题:这样的写法一般要考虑是否会有不存在另外一端的可能性,但是这里不需要,读者可以考虑一下这个隐含的问题.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
const int N = 2e5+7;
char sa[N],sb[N];
int f[N],g[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
scanf("%s%s",sa + 1,sb + 1);
int have = 0;
forn(i,1,m)
{
while(sa[have] != sb[i]) ++have;
f[i] = have;
++have;
}
have = n + 1;
forr(i,1,m)
{
while(sa[have] != sb[i]) --have;
g[i] = have;
--have;
}
int res = 0;
forn(i,1,m - 1) res = max(res,g[i + 1] - f[i]);
printf("%d",res);
return 0;
}
D. Genius's Gambit
给定\(a,b,k\).构造两个二进制数\(x,y\),要求两个数恰好都有\(a\)个\(0\)和\(b\)个\(1\).同时\(x\geq y\),\(x-y\)的二进制表示里面恰好有\(k\)个数.
思路
相当麻烦的一道题,我就不太给出所有具体情况的考虑了,只给我的做法的思路.
在做了一下样例之后可以发现想要\(1\)的构造方法是构造形如
1 . . . . . . . . .0
0 . . . . . . . . .1
这样的两组数,这样的话中间差几个数就会形成几个\(1\).所以一个想法就是如此构造出\(k\)个\(1\)放在最后面,之后考虑贪心的放数.
接下来开始讨论:
\(a = 0\)时,只有\(k=0\)的时候有解.
\(k = 0\)时,直接对称的摆放就可以确保没有\(1\)出现了.
接下来一切情况保证\(k\geq 1,a\geq 1\).
考虑把上面的上\(0\)下\(1\)的放在最后面,那么可以算出上\(1\)下\(0\)的具体位置.接下来考虑贪心放置:在空位之中,应该尽量放置\(0\).在左端最左侧必须要有一个\(1\),保证没有前导\(0\).直接做就可以了.这个构造之后还可以反推出\(k > a + b -2\)是无解的.当然我的代码里写的是错的,但是过了的原因是我之后判断了前导\(0\)所以没出事.
在这一切都做完了之后,情况看起来很美好,这样的构造确实基本没得问题,但是在一些极端情况会出现前导\(0\),因为这个构造确实已经很正确了,所以大胆直接写一个检查一下是否存在前导\(0\),如果存在就直接认为无解.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7;
int ansA[N],ansB[N];
int main()
{
int a,b,k;scanf("%d%d%d",&a,&b,&k);
if(a == 0 && k != 0) return puts("No"),0;
if(k == 0)
{
puts("Yes");
forn(i,1,b) ansA[i] = 1,ansB[i] = 1;
forn(i,b + 1,a + b) ansA[i] = 0,ansB[i] = 0;
forn(i,1,a + b) printf("%d",ansA[i]);puts("");
forn(i,1,a + b) printf("%d",ansB[i]);puts("");
return 0;
}
if(k > a + b - 1) return puts("No"),0;
ansA[a + b] = 0;ansB[a + b] = 1;
ansA[a + b - k] = 1;ansB[a + b - k] = 0;
int p = a + b - 1,f = a + b - k,L = a + b;
--a;--b;
forn(i,f + 1,p)
{
if(a > 0)
{
ansA[i] = 0;ansB[i] = 0;
--a;
}
else
{
ansA[i] = 1;ansB[i] = 1;
--b;
}
}
forn(i,1,f - 1)
{
if(b > 0)
{
ansA[i] = 1;ansB[i] = 1;
--b;
}
else
{
ansA[i] = 0;ansB[i] = 0;
--a;
}
}
forn(i,1,L) if(ansA[i] == 1) break;else return puts("No"),0;
forn(i,1,L) if(ansB[i] == 1) break;else return puts("No"),0;
puts("Yes");
forn(i,1,L) printf("%d",ansA[i]);puts("");
forn(i,1,L) printf("%d",ansB[i]);puts("");
return 0;
}
E. Almost Fault-Tolerant Database
给定一个\(n\)个长度为\(m\)的数组,要求构造一个长度为\(m\)的数组且与每个数组之间不同的数的个数不超过\(2\).输出构造方案或报告无解.
思路
一个比较显然而且直接的思路是直接把第一个数组掰下来作为可能的答案,再去和每个数组比对看是否可能是答案.那么,最开始复制一下第一组数组作为答案数组,再顺次考虑.
-
当某个数组与答案不同位置超过\(5\)的时候,必然无解.所以不同的情况只有\(3,4\)两种.
-
存在两个数组,不同位置个数是\(4\),且四个位置互不相交的时候同样无解.
第二个性质不太好直接运用,但是他可以简化代码难度,详见后方.
思路就比较直接了,枚举每个数组算一下不同位置的个数,踢掉无解情况,接下来可以找到第一个不算无解,但是必须要修改某些数的第一个数组.把他的下标记作\(pos1\).显然的是,这里必须要修改答案数组中的某些数,以使的不同位置的个数不超过\(2\).我们可以把所有不同的位置抠出来并且枚举谁需要修改.
在之后,可以继续枚举一层,找到第一个必须要做修改的数组,此时,由于第二个性质的存在,我们只需要再次枚举不同个数是\(3\)的数组的不同位置拿来修改答案数组的可能性,可以简化很多情况.然后检查一下是否合法即可.
在两个过程中,如果已经找不到一个必须要修改的数组,那么说明当前答案已经合法了,直接输出即可.
额外说明:为什么只需要枚举其中一个修改的,如果第一层枚举的时候,不同的位置有四个呢?因为不存在两个不同个数是\(4\)且互不相交的数组,所以如果第一层有\(4\)个,那么合理的情况必须要是修改了其中一个之后另外一个数组重叠上了修改的位置使得答案合法.在\(4\)个的情况下,修改两个之后另外一个如果不重叠上也只能是无解的,所以这样直接的去做也恰好是正确的.而且好写.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int n,m;
bool check(vector<vector<int>>& a,vector<int>& ans)
{
forn(i,1,n)
{
int cnt = 0;
forn(j,1,m) if(ans[j] != a[i][j]) ++cnt;
if(cnt >= 3) return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
vector<vector<int>> a(n + 17,vector<int>(m + 17));
forn(i,1,n) forn(j,1,m) scanf("%d",&a[i][j]);
vector<int> ans(m + 17);
forn(i,1,m) ans[i] = a[1][i];
int pos1 = -1,pos2 = -1;
vector<int> bad;
forn(i,2,n)
{
int cnt = 0;
forn(j,1,m) if(ans[j] != a[i][j]) ++cnt;
if(cnt <= 2) continue;
if(cnt >= 5) return puts("No"),0;
pos1 = i;
forn(j,1,m) if(ans[j] != a[i][j]) bad.push_back(j);
break;
}
if(pos1 == -1)
{
puts("Yes");
forn(i,1,m) printf("%d ",ans[i]);
return 0;
}
for(auto& chd : bad)
{
vector<int> bad_succ;
int prev = ans[chd],pos2 = -1;
ans[chd] = a[pos1][chd];
forn(i,2,n)
{
int cnt = 0;
forn(j,1,m) if(ans[j] != a[i][j]) ++cnt;
if(cnt <= 2) continue;
if(cnt >= 3)
{
pos2 = i;
forn(j,1,m) if(ans[j] != a[i][j]) bad_succ.push_back(j);
break;
}
}
if(pos2 == -1)
{
puts("Yes");
forn(i,1,m) printf("%d ",ans[i]);
return 0;
}
if(bad_succ.size() == 3)
{
for(auto& chd_succ : bad_succ)
{
int succ_prev = ans[chd_succ];
ans[chd_succ] = a[pos2][chd_succ];
if(check(a,ans))
{
puts("Yes");
forn(i,1,m) printf("%d ",ans[i]);
return 0;
}
ans[chd_succ] = succ_prev;
}
}
ans[chd] = prev;
}
puts("No");
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步