Codeforces round 649 赛后解题报告
A. XXXXX
这个题是 1h 50min 才做出来的
首先我们先来关注一句话:
An array a a a is a subarray of an array b b b if a can be obtained from b b b by deletion of several (possibly, zero or all) elements from the beginning and several (possibly, zero or all) elements from the end.
注意 subarray。所以是一段连续的子序列啊啊啊啊啊。
所以我们现在的思路就很明确了。
1.如果
∀
i
≤
n
,
x
∣
a
i
\forall i\leq n,x\mid a_i
∀i≤n,x∣ai,答案肯定为
−
1
-1
−1。
2.如果
x
∤
∑
i
=
1
n
a
i
x\nmid \sum_{i=1}^n a_i
x∤∑i=1nai,我们 直接输出
n
n
n 即可。
3.问题现在集中在如果不属于前两种情况,即
x
∤
∑
i
=
1
n
a
i
x\nmid \sum_{i=1}^n a_i
x∤∑i=1nai,且不是所有的
a
i
a_i
ai 都是
x
x
x 的倍数。这种情况我们可以从前往后扫一遍,直到出现不是
x
x
x 的倍数的数就可以停下来,从后往前进行相同的操作,最后比较一下那种更优即可。
为什么?我们知道对于两个
x
x
x 的倍数
a
,
b
a,b
a,b。我们设
a
=
p
⋅
x
,
b
=
q
⋅
x
a=p\cdot x,b=q\cdot x
a=p⋅x,b=q⋅x。
a
−
b
=
(
p
−
q
)
⋅
x
a-b=(p-q)\cdot x
a−b=(p−q)⋅x,也是
x
x
x 的倍数,所以我们要找到一个不能被
x
x
x 整除的数才可以。**我们最后要在“从前往后”和“从后往前”得出的答案中选一个。 原因很简单,自己想想就可以知道的对吧。那为啥我没想到呢/kk。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
int n,a[maxn],cnt[maxn],x;
signed main() {
int t;
t=read();
while(t--) {
fill(cnt,cnt+n+1,0);
n=read();x=read();
int sum=0;
for(int i=1;i<=n;i++) {
a[i]=read();
cnt[a[i]%x]++;
sum+=a[i]%x;
}
if(cnt[0]==n) {
cout<<-1<<endl;
continue;
}
else if(sum%x!=0) {
cout<<n<<endl;
continue;
}
int left=1,right=n;
for(;left<=n;left++) {
if(a[left]%x!=0) break;
}
for(;right>=1;right--) {
if(a[right]%x!=0) {
break;
}
}
cout<<max(n-left,right-1)<<endl;
}
return 0;
}
B. Most socially-distanced subsequence
这个题比 A 简单
我们先来看一个单调递增的区间: a 1 , a 2 , a 3 . . . a k − 1 , a k a_1,a_2,a_3...a_{k-1},a_k a1,a2,a3...ak−1,ak,满足 a 1 < a 2 < a 3 < . . . < a k − 1 < a k a_1<a_2<a_3<...<a_{k-1}<a_k a1<a2<a3<...<ak−1<ak。
对于这个区间,如果我们选择了
a
b
1
,
a
b
2
,
a
b
3
.
.
.
a
b
q
−
1
,
a
b
q
a_{b_1},a_{b_2},a_{b_3}...a_{b_{q-1}},a_{b_q}
ab1,ab2,ab3...abq−1,abq,其中
b
1
<
b
2
<
b
3
<
.
.
.
<
b
q
−
1
<
b
q
≤
n
b_1<b_2<b_3<...<b_{q-1}<b_q\leq n
b1<b2<b3<...<bq−1<bq≤n,此时我们得到的答案为
a
n
s
=
(
(
a
b
2
−
a
b
1
)
+
(
a
b
3
−
a
b
2
)
+
.
.
.
+
(
a
b
q
−
a
b
q
−
1
)
)
ans=((a_{b_2}-a_{b_1})+(a_{b_3}-a_{b_2})+...+(a_{b_q}-a_{b_{q-1}}))
ans=((ab2−ab1)+(ab3−ab2)+...+(abq−abq−1))
a
n
s
=
a
b
q
−
a
b
1
ans=a_{b_q}-a_{b_1}
ans=abq−ab1
由此可得:
a
n
s
m
a
x
=
a
k
−
a
1
ans_{max}=a_k-a_1
ansmax=ak−a1
单调递减的区间同理。
因此我们只需要找出每一个单调递增和单调递减的区间即可,看代码。
//#pragma GCC optimize("Ofast","-funroll-loops","-fdelete-null-pointer-checks")
//#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
int n,a[maxn],sum[maxn],x;
signed main() {
int t;
t=read();
while(t--) {
n=read();
for(int i=1;i<=n;i++) {
a[i]=read();
}
int ans=0,last=1,len=2;//last记录分界点
bool now=a[1]<a[2];//now记录递增还是递减,为1就是递增,0是递减,下同
for(int i=2;i<=n;i++) {
if(a[i]<a[i-1]==now) {
ans+=abs(a[i-1]-a[last]);
last=i-1;
now=1-now;//递增递减交换
len++;
}
}
ans+=abs(a[n]-a[last]);
cout<<len<<endl;
now=a[1]<a[2];
cout<<a[1]<<" ";
for(int i=2;i<=n;i++) {
if(a[i]<a[i-1]==now) {
printf("%lld ",a[i-1]);
now=1-now;//递增递减交换
}
}
cout<<a[n]<<endl;//注意一头一尾单独处理~
}
return 0;
}
C. Ehab and Prefix MEXs
这个题比 A 简单
这个题有一个很重要的条件
It’s guaranteed that a i ≤ a i + 1 a_i\leq a_i+1 ai≤ai+1 for 1 ≤ i < n 1\leq i<n 1≤i<n.
所以我们可以进行这样的处理,先找出所有的没出现的数字。如果 a i = a i − 1 a_i=a_{i-1} ai=ai−1。那么我们为了尽量让题目有解,一定要尽量把更大的数放进答案数组 b b b 里。如果 a i ≠ a i − 1 a_i\neq a_{i-1} ai=ai−1。我们就要把 a i − 1 a_{i-1} ai−1 放到 b i b_i bi 上,因为这个数字不再是最小未出现过的数了。其实这个题还是非常简单的。
什么时候无解呢?我们现在极限的来操作一下 b b b 数组
i= | 1 | 2 | 3 | 4 | 5 | 6 | … | i |
---|---|---|---|---|---|---|---|---|
b= | 0 | 1 | 2 | 3 | 4 | 5 | … | i-1 |
a= | 1 | 2 | 3 | 4 | 5 | 6 | … | i |
我们发现 a i a_i ai 的最大值为 i i i,所以如果输入时 a i > i a_i>i ai>i,输出 − 1 -1 −1 即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e6+10;
int n,a[maxn],b[maxn],maxx[maxn];
bool ex[maxn];
vector<int> v;//存放未出现的数字
signed main() {
n=read();
for(int i=1;i<=n;i++) {
a[i]=read();
ex[a[i]]=1;
if(a[i]!=0&&a[i]>i) {//注意 a_i!=0
cout<<-1<<endl;
return 0;
}
}
for(int i=0;i<=1e6;i++) {
if(!ex[i]){
v.push_back(i);
}
}
int index=0;//记录当前放到哪个数
for(int i=1;i<=n;i++) {
if(i==1||a[i]==a[i-1]) {
b[i]=v[index++];
}
else {
b[i]=a[i-1];
}
printf("%lld ",b[i]);
}
return 0;
}
D - Ehab’s Last Corollary
这个题是一道很好的“图论+树”的题。
推荐一道同一个人出的类似的题:Ehab’s Last Theorem
题目有云:
I have a proof that for any input you can always solve at least one of these problems, but it’s left as an exercise for the reader.
我们就来先证明一下。
我们要思考一个问题,什么时候我们能找到一个大小为 ⌈ k 2 ⌉ \lceil \frac{k}{2}\rceil ⌈2k⌉ 的独立集?只有当我们能在图中找到一个大小为 k k k 的树或一个大小为 k k k 的简单环时才可以,然而此时我们可以回答第一个问题,我们也就证明了一定有解。
简单环就是任意环上不相邻的点没有连线,即 ( v i , v i + 1 ) ∉ E (v_i,v_{i+1})\notin E (vi,vi+1)∈/E。
我们提供两种解法:
法一
首先我们来解决 n = k n=k n=k 的情况。这个时候如果图是一棵树,那么我们一定能找到独立集这个问题的解,否则就可回答第一个问题。
如果 n ≠ k n\neq k n=k,我们就找到一个大小为 k k k 的联通子图,用 n = k n=k n=k 的方法去做,问题就迎刃而解了。
那么我们来讲一下如何找到一个环(不限大小)。
我们先来看一个图:
首先,我们随便以一个点作为起点进行 DFS。我们以1位起点。那么,我把DFS中经过过的边(也就是DFS树),边权为 1 1 1,其他为 0 0 0。那么原图就变成了这个样子:
我们发现,由 1 1 1 边构成的图是一棵树,虽然这个性质对我们的解题毫无用处,但这个DFS树真的是没什么用,了解就好,他对我们的思路启发是有一点作用的。我们继续讲题,现在,如果在DFS时,我们从 u u u 出发,找到了一个点 v v v,如果之前 v v v 已经被搜到过了,那么我们就找到了一个环。这个环的大小就至少为 d f n u − d f n v + 1 dfn_u-dfn_v+1 dfnu−dfnv+1。( d f n dfn dfn 表第 i i i 个点被遍历到的时间戳)。
#include<bits/stdc++.h>
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
struct edge {
int v,next;
}e[maxn<<2];
int n,h[maxn],h2[maxn],cnt,m,k,cnt2;
bool ex[maxn],flag,is[maxn];
stack<int> s;
vector<int> ans[2];
void addedge(int u,int v) {
e[++cnt].v=v;
e[cnt].next=h[u];
h[u]=cnt;
}
void insert(int u,int v) {
addedge(u,v);
addedge(v,u);
}
void dfs(int u,int fa) {//找环
s.push(u);
ex[u]=1;
for(int i=h[u];i;i=e[i].next) {
int v=e[i].v;
if(fa!=v&&is[v]) {//is[v]判断答我们遍历的点是否在我们建的新图里
if(ex[v]) {//找到就输出
int sz=s.size();
for(int j=0;j<sz;j++) {
ans[0].push_back(s.top());
if(s.top()==v) {
break;
}
s.pop();
}
sz=ans[0].size();
printf("2\n%d\n",sz);
for(int j=0;j<sz;j++) {
printf("%d ",ans[0][j]);
}
exit(0);
}
dfs(v,u);
}
}
s.pop();
ex[u]=0;
}
void color(int u,int fa,int type) {//找独立集
ans[type].push_back(u);
for(int i=h[u];i;i=e[i].next) {
int v=e[i].v;
if(fa!=v&&is[v]) {
color(v,u,1-type);
}
}
}
void create(int u,int fa,int num) {//建新图
is[u]=1;
for(int i=h[u];i;i=e[i].next) {
int v=e[i].v;
if(!is[v]) {
if(flag) {
return ;
}
if(num==1) {
flag=1;
}
create(v,u,num-1);
}
}
}
int main() {
n=read();m=read();k=read();
for(int i=1;i<=m;i++) {
int a=read(),b=read();
insert(a,b);
}
m=0;
create(1,0,k-1);
dfs(1,0);
printf("1\n");
color(1,0,0);
if(ans[0].size()<ans[1].size()) {
swap(ans[0],ans[1]);
}
int sz=ans[0].size();
sz=min(sz,(k+1)/2);//判断一下我们找到的最大独立集的大小是否一定小于 ceil(k/2)
for(int i=0;i<sz;i++) {
printf("%d ",ans[0][i]);
}
return 0;
}
法二
我们先来找一个简单环,如果这个环的大小大于 k k k,染色法找独立集,否则直接输出。
E. X-OR
这个题是一道交互题。(蒟蒻第一次做交互题,所以一直不理解规则,于是。。。。)
我们来具体讲讲怎么做。
首先我们先了解一下什么叫“按位或”。“按位或”就是对于两个数的每一个二进制位上进行布尔运算。每一位 1 , 0 1,0 1,0 的结果如下表。
或 | 1 | 0 |
---|---|---|
1 | 1 | 1 |
0 | 1 | 0 |
我们再来了解一些“按位或”的性质。
性质1
max ( x , y ) ≤ x ∣ y \max(x,y)\leq x|y max(x,y)≤x∣y
证明1
因为 x , y x,y x,y 每一位上进行了“或”的操作后得到的结果,总归是大于等于这两个数的这一位二进制数。得证。
性质2
0 ∣ x = x 0|x=x 0∣x=x
证明2
0 0 0 的每一位二进制上都是 0 0 0,所以答案就等于 x x x。
知道这些后,我们就可以来做题啦。解法有点像模拟退火的思路?
我们发现
4269
4269
4269 这个数很有意思,我们又有
3
≤
n
≤
2048
3\leq n\leq 2048
3≤n≤2048。我们就会发现
4269
=
2048
×
2
+
173
4269=2048\times 2+173
4269=2048×2+173。一开始还以为应该是
4096
4096
4096,出题人打错了。。。。
所以我们可以考虑通过两次 n n n 次的询问,和一些小问题的处理。所以我们可以非常容易的相处一个方法。我们先用 n + k ( k ≤ 173 ) n+k(k\leq 173) n+k(k≤173) 次找出 0 0 0 的位置,在通过 n − 1 n-1 n−1 次询问就可以知道 P P P。问题就转化为如何在限定次数内找出 0 0 0 的位置。
首先我们随机找两个位置记为 x , y x,y x,y 作为 0 0 0 的可能所在的位置,记 v a l = P x ∣ P y val=P_x|P_y val=Px∣Py。我们随机一个位置 z ( z ≠ x , z ≠ y ) z(z\neq x,z\neq y) z(z=x,z=y)。
1.如果
P
x
∣
P
y
>
P
y
∣
P
z
P_x|P_y>P_y|P_z
Px∣Py>Py∣Pz,那么代表
P
x
≠
0
P_x\neq 0
Px=0。可由性质1得出,我们用
z
z
z 代替
x
x
x。
2.如果
P
x
∣
P
y
<
P
y
∣
P
z
P_x|P_y<P_y|P_z
Px∣Py<Py∣Pz,还则罢了。
3.如果
P
x
∣
P
y
=
P
y
∣
P
z
P_x|P_y=P_y|P_z
Px∣Py=Py∣Pz,
P
y
P_y
Py 一定不是
0
0
0,用
z
z
z 代替
y
y
y。
现在我们找到了两个可能为 0 0 0 的位 x , y x,y x,y。我们在他们之间只需要找出那个为 0 0 0 的位置即可。我们只需要用跟上面相同的方法,还是随机出一个 z z z,同样 z ≠ x , z ≠ y z\neq x,z\neq y z=x,z=y:
1.如果
P
x
∣
P
z
>
P
y
∣
P
z
P_x|P_z>P_y|P_z
Px∣Pz>Py∣Pz,那么
P
x
=
0
P_x=0
Px=0。
2.如果
P
x
∣
P
z
<
P
y
∣
P
z
P_x|P_z<P_y|P_z
Px∣Pz<Py∣Pz,那么
P
y
=
0
P_y=0
Py=0。
最后只需在询问一遍所有的位置便可得出答案。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=2100;
int n,tool[maxn],ans[maxn];
int PLA(int i1,int i2) {//Please Let me know the Answer
cout<<"?"<<" "<<i1<<" "<<i2<<endl;
cout.flush();//注意基本格式
return read();
}
signed main() {
srand(71179);//别乱想,没啥特殊意义(jiade)
n=read();
for(int i=1;i<=n;i++) {
tool[i]=i;
}
random_shuffle(tool+1,tool+n+1);
random_shuffle(tool+1,tool+n+1);//越随机越好
random_shuffle(tool+1,tool+n+1);
int x=tool[1],y=tool[2];
int v=PLA(x,y);
for(int i=3;i<=n;i++) {
int z=tool[i];
if(z==y||z==x) {
continue;
}
int tv=PLA(z,y);
if(tv<v) {
v=tv,x=z;
}
else if(tv==v) {
y=z;
v=PLA(x,y);
}
}
while(1) {
int z=tool[rand()%n+1];
if(z==x||z==y) continue;
int v1=PLA(x,z),v2=PLA(y,z);
if(v1==v2){
continue;
}
if(v1>v2) {
swap(x,y);
}
for(int i=1;i<=n;i++) {
if(i!=x) {
ans[i]=PLA(i,x);
}
}
cout<<"!";
for(int i=1;i<=n;i++) {
cout<<" "<<ans[i];
}
cout.flush();//注意基本格式
return 0;
}
return 0;
}