Codeforces #720 div2 A~E题解

视频讲解: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(1A,B106) ,找出三个不同的正整数 x , y , z x,y,z x,y,z
使得其满足以下两个条件:

  1. x , y , z x,y,z x,y,z中有且仅有一个数有一个能被 A ⋅ B A \cdot B AB 整除,另外两个数能被 A A A 整除;
  2. x + y = z x+y=z x+y=z

若能找到,则输出"YES"和任意解,若找不到,则输出"NO"。

题解

首先需要知道,若 A ⋅ B ∣ x A \cdot B| x ABx ,则 y ≡ z ( m o d A ⋅ B ) y \equiv z (mod A \cdot B) yz(modAB)

因此我们只需找出一个 A ⋅ B A \cdot B AB 的倍数作为 x x x,再找另一个是 A A A 的倍数但不是 A ⋅ B A \cdot B AB 的倍数的数作为 y y y ,那么 z z z 必然符合条件。

不妨设 x = A ⋅ B x=A \cdot B x=AB,对于 y y y 来说:
B = 1 B=1 B=1 时,不存在 A ∣ y A | y Ay A ⋅ B ∤ y A \cdot B \nmid y ABy ,无解;
B ≠ 1 B \neq 1 B=1 时,设 y = A y=A y=A z = A ⋅ B + A z=A \cdot B +A z=AB+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(2in) g c d ( a i − 1 , a i ) = 1 gcd(a_{i-1},a_i)=1 gcd(ai1,ai)=1

给定一个长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1n105) 的数组 a ( 1 ≤ a i ≤ 1 0 9 ) a(1 \leq a_i \leq 10^9) a(1ai109),你可以通过以下操作修改数组,使得数组变为“好数组”:

  • 选择两个不同的下标 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i,j(1 \leq i,j \leq n,i \neq j) i,j(1i,jn,i=j) 和两个整数 x , y ( 1 ≤ x , y ≤ 2 ⋅ 1 0 9 ) x,y(1 \leq x,y \leq 2 \cdot 10^9) x,y(1x,y2109),且满足 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 2109 ,而题目要求使得相邻两个数的 g c d = 1 gcd=1 gcd=1

因此不妨考虑将数组中的偶数位置变为一个在 ( 1 0 9 , 2 ⋅ 1 0 9 ) (10^9,2 \cdot 10^9) (109,2109) 范围内的素数,比如 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(ai1,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) (1t2),(1i,jn,i=j),(1xn1)。根据 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 23n+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=n1 时, 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(n1,pi),min(n,pj))=max(min(n1,pi),pj)
  • t = 2 , x = n − 1 t=2,x=n-1 t=2,x=n1 时, 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(n1,pi),max(n,pj))=max(n1,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=n1,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(n1,1),min(n,pj))=pj

因此只要我们找到最大值 n n n 或最小值 1 1 1 在排序中所在的下标,通过 ⑤ 或 ⑥ 形式的询问,即可得到任意位置的值。
由于最多只能询问 ⌊ 3 ⋅ n 2 ⌋ + 30 \lfloor \frac{3 \cdot n}{2} \rfloor +30 23n+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 n1,则有以下几种情况
    • 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=n1,pj<n1
    • p j = n − 1 p_j=n-1 pj=n1
      此时将 i i i j j j 互换再询问一次,即可确认是否有 p i = n p_i=n pi=n 存在

因为每次询问会排除两个数,且只有遇到值为 n − 1 n-1 n1 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 23n+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

题目大意

给定一棵树,通过若干次操作,使其变为一条链。每次操作有两步:

  1. 删除图上的一条边
  2. 在两个不同的点间添加一条边

求最小操作次数和具体操作步骤

题解

题意要求将树变成链。一个初步的想法就是,用dfs遍历整棵树,若有一个节点有多余1个儿子,就将其拆分重连,如图所示。
在这里插入图片描述

但是题目要求最小的操作次数,在下图中,虽然可以得到链,但操作数并不是最小的。
在这里插入图片描述
因此当一个节点具有2个儿子时,需要将该节点及其父节点的边断开。当有3个及以上儿子时,再断开该节点及其儿子的边。具体而言,对于一个节点的每个儿子:

  • s o n N u m ≤ 1 sonNum \leq 1 sonNum1 ,构成一条链,保留;
  • 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(1m105) 个数,其中值为 i ( 1 ≤ i ≤ 1 0 5 ) i(1 \leq i \leq 10^5) i(1i105) 的数有 a i ( 0 ≤ a i ≤ m ) a_i(0 \leq a_i \leq m) ai(0aim) 个。
需要将这些数放到一个 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 n22n2m ,即除了白色格子,其他格子总数不小于 m m m
  • n ∗ ⌈ n 2 ⌉ ≥ m a x ( a i ) n*{\lceil \frac{n}{2} \rceil} \geq max(a_i) n2nmax(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("");
		}
	}
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值