视频讲解:BV1hK4y1A7dk
A. Nastia and nearly Good Numbers
题目大意
给定两个正整数
A
,
B
(
1
≤
A
,
B
≤
1
0
6
)
A,B(1 \leq A,B \leq 10^6)
A,B(1≤A,B≤106) ,找出三个不同的正整数
x
,
y
,
z
x,y,z
x,y,z ,
使得其满足以下两个条件:
- x , y , z x,y,z x,y,z中有且仅有一个数有一个能被 A ⋅ B A \cdot B A⋅B 整除,另外两个数能被 A A A 整除;
- x + y = z x+y=z x+y=z
若能找到,则输出"YES"和任意解,若找不到,则输出"NO"。
题解
首先需要知道,若 A ⋅ B ∣ x A \cdot B| x A⋅B∣x ,则 y ≡ z ( m o d A ⋅ B ) y \equiv z (mod A \cdot B) y≡z(modA⋅B) 。
因此我们只需找出一个 A ⋅ B A \cdot B A⋅B 的倍数作为 x x x,再找另一个是 A A A 的倍数但不是 A ⋅ B A \cdot B A⋅B 的倍数的数作为 y y y ,那么 z z z 必然符合条件。
不妨设
x
=
A
⋅
B
x=A \cdot B
x=A⋅B,对于
y
y
y 来说:
当
B
=
1
B=1
B=1 时,不存在
A
∣
y
A | y
A∣y 且
A
⋅
B
∤
y
A \cdot B \nmid y
A⋅B∤y ,无解;
当
B
≠
1
B \neq 1
B=1 时,设
y
=
A
y=A
y=A,
z
=
A
⋅
B
+
A
z=A \cdot B +A
z=A⋅B+A 即可满足条件;
参考代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long T,a,b;
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&a,&b);
if(b==1)
printf("NO\n");
else
{
printf("YES\n");
printf("%lld %lld %lld\n",a,a*b,a*b+a);
}
}
}
B. Nastia and a Good Array
题目大意
定义若数组 a a a 满足以下条件,则称其为“好数组”:
- 对于任意 i ( 2 ≤ i ≤ n ) i(2 \leq i \leq n) i(2≤i≤n) , g c d ( a i − 1 , a i ) = 1 gcd(a_{i-1},a_i)=1 gcd(ai−1,ai)=1
给定一个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105) 的数组 a ( 1 ≤ a i ≤ 1 0 9 ) a(1 \leq a_i \leq 10^9) a(1≤ai≤109),你可以通过以下操作修改数组,使得数组变为“好数组”:
- 选择两个不同的下标 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i,j(1 \leq i,j \leq n,i \neq j) i,j(1≤i,j≤n,i=j) 和两个整数 x , y ( 1 ≤ x , y ≤ 2 ⋅ 1 0 9 ) x,y(1 \leq x,y \leq 2 \cdot 10^9) x,y(1≤x,y≤2⋅109),且满足 m i n ( a i , a j ) = m i n ( x , y ) min(a_i,a_j)=min(x,y) min(ai,aj)=min(x,y),使得 a i a_i ai 变为 x x x 且 a j a_j aj 变为 y y y 。
题解
首先先理解题意中的操作。该操作实际上是选择数组上的两个数,使得其中的较小值不变,较大值变为指定的数,并且这两个数可以选择是否交换位置。
再看题目的数据范围, a i a_i ai 最大为 1 0 9 10^9 109,但 x , y x,y x,y 最大可以有 2 ⋅ 1 0 9 2 \cdot 10^9 2⋅109 ,而题目要求使得相邻两个数的 g c d = 1 gcd=1 gcd=1 。
因此不妨考虑将数组中的偶数位置变为一个在 ( 1 0 9 , 2 ⋅ 1 0 9 ) (10^9,2 \cdot 10^9) (109,2⋅109) 范围内的素数,比如 1 0 9 + 7 10^9+7 109+7,奇数位变为 1 0 9 10^9 109 以下的数,这样必定满足 g c d ( a i − 1 , a i ) = 1 gcd(a_{i-1},a_i)=1 gcd(ai−1,ai)=1 。
具体实现而言,每次的操作为 i = p , j = p + 1 , x = m i n ( a p , a p + 1 ) , y = 1 0 9 + 7 i=p,j=p+1,x=min(a_p,a_{p+1}),y=10^9+7 i=p,j=p+1,x=min(ap,ap+1),y=109+7 ,其中 p p p 为小于 n n n 的奇数,即可满足条件。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100100;
const int mx=1e9+7;
int a[MAXN];
int main()
{
int T,i,n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
printf("%d\n",n/2);
for(i=1;i<n;i+=2)
printf("%d %d %d %d\n",i,i+1,min(a[i],a[i+1]),mx);
}
}
C. Nastia and a Hidden Permutation
题目大意
这是一道交互题,让你猜一个包含从 1 1 1 到 b b b 的排序 p p p。你可以输出 4 4 4 个整数 t , i , j , x t,i,j,x t,i,j,x 进行询问,其中 ( 1 ≤ t ≤ 2 ) , ( 1 ≤ i , j ≤ n , i ≠ j ) , ( 1 ≤ x ≤ n − 1 ) (1 \leq t \leq 2),(1 \leq i,j \leq n,i \neq j),(1 \leq x \leq n-1) (1≤t≤2),(1≤i,j≤n,i=j),(1≤x≤n−1)。根据 t t t 的不同,系统会回答:
- t = 1 : m a x ( m i n ( x , p i ) , m i n ( x + 1 , p j ) ) t=1:max(min(x,p_i),min(x+1,p_j)) t=1:max(min(x,pi),min(x+1,pj))
- t = 2 : m i n ( m a x ( x , p i ) , m a x ( x + 1 , p j ) ) t=2:min(max(x,p_i),max(x+1,p_j)) t=2:min(max(x,pi),max(x+1,pj))
最多询问 ⌊ 3 ⋅ n 2 ⌋ + 30 \lfloor \frac{3 \cdot n}{2} \rfloor +30 ⌊23⋅n⌋+30 次。
题解
首先分析这个回答,其中包含3次最大最小的取值,考虑对其简化。
发现当
x
x
x 取极值时,可以进行简化:
- 当 t = 1 , x = 1 t=1,x=1 t=1,x=1 时, m a x ( m i n ( 1 , p i ) , m i n ( 2 , p j ) ) = m i n ( 2 , p j ) max(min(1,p_i),min(2,p_j))=min(2,p_j) max(min(1,pi),min(2,pj))=min(2,pj) ①
- 当 t = 2 , x = 1 t=2,x=1 t=2,x=1 时, m i n ( m a x ( 1 , p i ) , m a x ( 2 , p j ) ) = m i n ( p i , m a x ( 2 , p j ) ) min(max(1,p_i),max(2,p_j))=min(p_i,max(2,p_j)) min(max(1,pi),max(2,pj))=min(pi,max(2,pj)) ②
- 当 t = 1 , x = n − 1 t=1,x=n-1 t=1,x=n−1 时, m a x ( m i n ( n − 1 , p i ) , m i n ( n , p j ) ) = m a x ( m i n ( n − 1 , p i ) , p j ) max(min(n-1,p_i),min(n,p_j))=max(min(n-1,p_i),p_j) max(min(n−1,pi),min(n,pj))=max(min(n−1,pi),pj) ③
- 当 t = 2 , x = n − 1 t=2,x=n-1 t=2,x=n−1 时, m i n ( m a x ( n − 1 , p i ) , m a x ( n , p j ) ) = m a x ( n − 1 , p i ) min(max(n-1,p_i),max(n,p_j))=max(n-1,p_i) min(max(n−1,pi),max(n,pj))=max(n−1,pi) ④
当 p i p_i pi 或 p j p_j pj取极值时, ② 和 ③ 还可以进一步简化:
- 当 t = 2 , x = 1 , p j = n t=2,x=1,p_j=n t=2,x=1,pj=n 时, m i n ( m a x ( 1 , p i ) , m a x ( 2 , n ) ) = p i min(max(1,p_i),max(2,n))=p_i min(max(1,pi),max(2,n))=pi ⑤
- 当 t = 1 , x = n − 1 , p i = 1 t=1,x=n-1,p_i=1 t=1,x=n−1,pi=1 时, m a x ( m i n ( n − 1 , 1 ) , m i n ( n , p j ) ) = p j max(min(n-1,1),min(n,p_j))=p_j max(min(n−1,1),min(n,pj))=pj ⑥
因此只要我们找到最大值
n
n
n 或最小值
1
1
1 在排序中所在的下标,通过 ⑤ 或 ⑥ 形式的询问,即可得到任意位置的值。
由于最多只能询问
⌊
3
⋅
n
2
⌋
+
30
\lfloor \frac{3 \cdot n}{2} \rfloor +30
⌊23⋅n⌋+30 次,因此我们需要在
⌊
n
2
⌋
+
30
\lfloor \frac{n}{2} \rfloor +30
⌊2n⌋+30 次内找到最大值或最小值,约等于进行
n
2
\frac{n}{2}
2n 次询问。
再看前面推导得到的式子,发现利用 ② 或 ③ 找到最大值或最小值。
以寻找最大值为例,每次选取两个未被选取过的
i
,
j
i,j
i,j 进行 ③ 询问,判断其返回值:
- 若返回值为 n n n ,则表示 p j = n p_j=n pj=n
- 若返回值为
n
−
1
n-1
n−1,则有以下几种情况
- p i = n p_i=n pi=n
- p i = n − 1 , p j < n − 1 p_i=n-1,p_j < n-1 pi=n−1,pj<n−1
-
p
j
=
n
−
1
p_j=n-1
pj=n−1
此时将 i i i 和 j j j 互换再询问一次,即可确认是否有 p i = n p_i=n pi=n 存在
因为每次询问会排除两个数,且只有遇到值为 n − 1 n-1 n−1 或 n n n 时会有二次询问,因此寻找最大值的询问次数必定小于 ⌊ n 2 ⌋ + 30 \lfloor \frac{n}{2} \rfloor +30 ⌊2n⌋+30 次,总次数小于 ⌊ 3 ⋅ n 2 ⌋ + 30 \lfloor \frac{3 \cdot n}{2} \rfloor +30 ⌊23⋅n⌋+30 次,满足题意。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=10010;
int p[MAXN];
int main()
{
int T,n,mxid,i,j,ans;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i+=2)
{
j=i%n+1;
printf("? %d %d %d %d\n",1,i,j,n-1);
fflush(stdout);
scanf("%d",&ans);
if(ans==n)
{
mxid=j;
break;
}
if(ans==n-1)
{
printf("? %d %d %d %d\n",1,j,i,n-1);
fflush(stdout);
scanf("%d",&ans);
if(ans==n)
{
mxid=i;
break;
}
}
}
p[mxid]=n;
for(i=1;i<=n;i++)
{
if(i==mxid)
continue;
printf("? %d %d %d %d\n",2,i,mxid,1);
fflush(stdout);
scanf("%d",&ans);
p[i]=ans;
}
printf("!");
for(i=1;i<=n;i++)
printf(" %d",p[i]);
puts("");
fflush(stdout);
}
}
D. Nastia Plays with a Tree
题目大意
给定一棵树,通过若干次操作,使其变为一条链。每次操作有两步:
- 删除图上的一条边
- 在两个不同的点间添加一条边
求最小操作次数和具体操作步骤
题解
题意要求将树变成链。一个初步的想法就是,用dfs遍历整棵树,若有一个节点有多余1个儿子,就将其拆分重连,如图所示。
但是题目要求最小的操作次数,在下图中,虽然可以得到链,但操作数并不是最小的。
因此当一个节点具有2个儿子时,需要将该节点及其父节点的边断开。当有3个及以上儿子时,再断开该节点及其儿子的边。具体而言,对于一个节点的每个儿子:
- 若 s o n N u m ≤ 1 sonNum \leq 1 sonNum≤1 ,构成一条链,保留;
- 若 s o n N u m = 2 sonNum =2 sonNum=2 ,断开 < v , p v > <v,p_v> <v,pv> 边;
- 若 s o n N u m > 2 sonNum >2 sonNum>2 ,断开 < v , s o n > <v,son> <v,son> 边;
这样将一棵树拆成了若干链,最后再逐个连接起来即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100100;
vector<int> vec[MAXN];
int xx1[MAXN],xx2[MAXN],xx3[MAXN],xx4[MAXN];
int cnt=0;
int dfs(int x,int fa)
{
int sonnum=0,ret=x;
for(int i=0;i<vec[x].size();i++)
{
int son=vec[x][i];
if(son==fa)
continue;
int endv=dfs(son,x);
if(endv<0)
continue;
sonnum++;
if(sonnum==1)
ret=endv;
else if(sonnum==2)
{
xx1[cnt]=fa;
xx2[cnt]=x;
xx3[cnt]=ret;
xx4[cnt++]=endv;
ret=-1;
}
else
{
xx1[cnt]=x;
xx2[cnt]=son;
xx3[cnt]=son;
xx4[cnt++]=endv;
}
}
return ret;
}
int main()
{
int T,n,i,root,a,b;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
vec[i].clear();
for(i=1;i<n;i++)
{
scanf("%d %d",&a,&b);
vec[a].push_back(b);
vec[b].push_back(a);
}
for(i=1;i<=n;i++)
{
if(vec[i].size()==1)
{
root=i;
break;
}
}
cnt=0;
int bef=dfs(root,-1);
printf("%d\n",cnt);
for(i=0;i<cnt;i++)
{
printf("%d %d %d %d\n",xx1[i],xx2[i],xx3[i],bef);
bef=xx4[i];
}
}
}
E. Nastia and a Beautiful Matrix
题目大意
给定
m
(
1
≤
m
≤
1
0
5
)
m(1 \leq m \leq 10^5)
m(1≤m≤105) 个数,其中值为
i
(
1
≤
i
≤
1
0
5
)
i(1 \leq i \leq 10^5)
i(1≤i≤105) 的数有
a
i
(
0
≤
a
i
≤
m
)
a_i(0 \leq a_i \leq m)
ai(0≤ai≤m) 个。
需要将这些数放到一个
n
×
n
n \times n
n×n 的矩阵内,使得该矩阵中任意一个
2
×
2
2 \times 2
2×2 的子矩阵,满足以下条件:
- 放置的数占有的格子数不超过 3 3 3 个
- 对角线上两个数字不能相同
求最小的 n n n 并输出矩阵。
题解
明显是一道构造题。
首先考虑不放任何数的空白格应该怎么分布。由于矩阵要求最小,所以空白格也应该最小。可以发现,当空白格的横纵坐标都为偶数时,例如第二行第二列,构造出的矩阵中的任意
2
×
2
2 \times 2
2×2 的子矩阵内都有一个空白格。如下图的白色格子所示
然后可以发现,在任意
2
×
2
2 \times 2
2×2 的子矩阵内,蓝色格子的对角线都是白色格子。因此蓝色格子放置的数字不受限制。
对于红色格子和黄色格子而言,在任意
2
×
2
2 \times 2
2×2 的子矩阵内他们互为对角线格子。因此红色格子和黄色格子应该包含不同颜色的数字。
具体实现时,可以从数量最多的数字开始,优先摆放红色格子,再摆放蓝色格子,最后摆放黄色格子。
最后考虑矩阵大小 n n n,其应该满足两个条件:
- n 2 − ⌊ n 2 ⌋ 2 ≥ m n^2- {\lfloor \frac{n}{2} \rfloor}^2 \geq m n2−⌊2n⌋2≥m ,即除了白色格子,其他格子总数不小于 m m m
- n ∗ ⌈ n 2 ⌉ ≥ m a x ( a i ) n*{\lceil \frac{n}{2} \rceil} \geq max(a_i) n∗⌈2n⌉≥max(ai) ,即红色格子加蓝色格子总数不小于数量最多的数字的数量。
确定 n n n 时,用二分或者循环遍历都可以,因为总时间复杂度 O ( n 2 ) O(n^2) O(n2),不差这一点。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100100;
const int MAXM=1010;
int a[MAXN],id[MAXN],l,k,m;
int ans[MAXM][MAXM];
bool cmp(int x,int y)
{
return a[x]>a[y];
}
int nxt()
{
while(l<=k&&!a[id[l]])
l++;
a[id[l]]--;
return id[l];
}
int main()
{
int T,i,j,mx,siz;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&m,&k);
mx=0;
for(i=1;i<=k;i++)
{
scanf("%d",&a[i]);
id[i]=i;
mx=max(mx,a[i]);
}
sort(id+1,id+k+1,cmp);
id[k+1]=0;
for(siz=1;;siz++)
if(m<=siz*siz-(siz/2)*(siz/2)&&mx<=siz*((siz+1)/2))
break;
l=1;
for(i=1;i<=siz;i+=2)
for(j=2;j<=siz;j+=2)
ans[i][j]=nxt();
for(i=1;i<=siz;i+=2)
for(j=1;j<=siz;j+=2)
ans[i][j]=nxt();
for(i=2;i<=siz;i+=2)
for(j=1;j<=siz;j+=2)
ans[i][j]=nxt();
printf("%d\n",siz);
for(i=1;i<=siz;i++)
{
for(j=1;j<=siz;j++)
printf("%d ",ans[i][j]);
puts("");
}
}
}