Codeforces Round #720 (Div. 2)

Codeforces Round #720 (Div. 2)

A

给定正整数 \(A,B\)

\(A\times B|C\),称 \(C\) 为“好数”。

\(A\times B\not |C\)\(A|C\),称 \(C\) 为“接近好数”。

要求找出互不相同的正整数 \(x,y,z\),使他们中恰好有一个数是“好数”且另外两个数是“接近好数”,且满足 \(x+y=z\)

不难发现,若 \(B=1\) 则一定无解。

否则一般情况下可以构造:\(x=A,y=A\times(B-1),z=A\times B\) 来符合要求。

但是互不相同限制了当 \(B=2\) 时我们不能这么干。

但是在 \(B=2\) 时直接考虑一下,不难发现 \(x=A,y=A\times(B+1),z=A\times(B+2)\) 符合要求。

于是简单讨论一下之后,这道题结束。

#include<cstdio>
using namespace std;

int main(){
	int T; scanf("%d", &T);
	while(T --){
		int a, b;
		scanf("%d %d", &a, &b);
		if(b == 1) puts("NO");
		else{
			puts("YES");
			if(b == 2) b = 4;
			long long x = a, y = 1LL * a * (b - 1), z = 1LL * a * b;
			printf("%lld %lld %lld\n", x, y, z);
		}
	}
	return 0;
}

B

给定一个数列 \(A\),每次操作形如 \((i,j,x,y)\),要求是 \(min(a(i),a(j))=min(x,y)\)

操作结束后,\(a(i)=x,a(j)=y\)

要求任意构造一组操作,使得操作后 \(A\) 中任意相邻的两个数均互质。

题目不要求操作数最小化

数据范围:\(a(i)\leq 10^9,x,y\leq 2\times 10^9\)

有了最后一句话就有了无限大的发展空间。

显然一个大于所有 \(a(i)\) 的大质数是与任何序列中的原数互质的,例如 \(p=10^9+7\)

那么只需要将序列 \(A=a_1,a_2,\dots,a_n\),变为 \(A'=min(a_1,a_2),p,min(a_3,a_4),p,\dots\) 即可。

#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 100010;
const int INF = 1e9 + 7;
int T, n, a[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int main(){
	T = read();
	while(T --){
		n = read();
		for(int i = 1; i <= n; i ++) a[i] = read();
		printf("%d\n", n / 2);
		for(int i = 1; i + 1 <= n; i += 2){
			printf("%d %d %d %d\n", i, i + 1, min(a[i], a[i + 1]), INF);
		}
	}
	return 0;
}

C

交互题。让你猜一个 \(n\) 排列。

每次询问形如 \((t,i,j,x)\),返回值为:

  • \(t = 1\)\(\max{(\min{(x, p_i)}, \min{(x + 1, p_j)})}\)
  • \(t = 2\)\(\min{(\max{(x, p_i)}, \max{(x + 1, p_j)})}\)

其中 \(1\leq x\leq n-1\)询问次数不得超过 \(\lfloor \frac {3 \cdot n} {2} \rfloor + 30\) 次。

很好的交互题。

猜排列的策略一般为,先确定一个特殊的数(如 \(1/n\)),然后根据特殊数再推出每个位置的数。

假设 \(1\) 的位置为 \(p\),不难发现,利用询问 \((1,p,i,n-1)\) 可以直接确定位置 \(i\) 上的数。

那么任务变为用 \(\lfloor \frac {n} {2} \rfloor + 30\) 次询问得到 \(1\) 的位置。

再次观察操作,发现只有当 \((2,i,i+1,1)\leq 2\)\(i,i+1\) 中才可能有 \(1\) 的出现。

此时再利用 \((1,i,i+1,1)\)\((1,i+1,i,1)\) 即可确定 \(1\) 的位置。

这样的询问数显然不超过限制条件。

#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 20010;
int T, n, ans[N];

int main(){
	scanf("%d", &T);
	while(T --){
		scanf("%d", &n);
		int pos = 0, now;
		for(int i = 1; i + 1 <= n; i += 2){
			printf("? 2 %d %d 1\n", i, i + 1);
			fflush(stdout); scanf("%d", &now);
			if(now <= 2){
				int x, y;
				printf("? 1 %d %d 1\n", i, i + 1);
				fflush(stdout); scanf("%d", &x);
				printf("? 1 %d %d 1\n", i + 1, i);
				fflush(stdout); scanf("%d", &y);
				if(x == 1) {pos = i + 1; break;}
				if(y == 1) {pos = i; break;}
			}
		}
		if(!pos) pos = n;
		ans[pos] = 1;
		for(int i = 1; i <= n; i ++) if(i != pos){
			printf("? 1 %d %d %d\n", pos, i, n - 1);
			fflush(stdout);
			scanf("%d", &ans[i]);
		}
		printf("!");
		for(int i = 1; i <= n; i ++) printf(" %d", ans[i]);
		puts("");
		fflush(stdout);
	}
	return 0;
}

D

给定一棵 \(n\) 个节点的树,规定一次操作为 短一条边 + 连一条边。

构造操作数最小的方案,使得树变为一条链。

个人认为比 C 简单。

一开始的想法是直接选一个度数最大的节点为根,然后简单贪心。

每次将多余的子节点断开后连接到上一个子节点为首的链的下端,根能包含两个子节点,其余只能包含一个。

正解其实差不多,假设处理到的节点的子节点数为 \(x\)

  • \(x\leq 2\),啥也不干。
  • \(x=3\),断开这个节点和它的父亲节点(如果有的话)。
  • \(x>3\),断开这个节点和它的父亲节点(如果有的话),然后任意断开 \(x-2\) 条边。

每次断开是真的断开,需要删边,而不是像上述 native 做法一样的简单处理。

这样贪心的好处是可以缩小父亲节点的子节点数,这样就减少了很多 native 想法中的操作。

删边时记录答案,最后还要单独处理出每条链的 起始节点 和 结束节点。

显然链的数量 = 删边数 + 1,所以每次利用两条链(顺序随意)输出答案即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 100010;
int T, n, cnt, head[N], deg[N];
bool Delet[N << 1], vis[N];
struct Edge{int nxt, to;} ed[N << 1];
vector<pair<int, int> > ans1, ans2;

void Clear(){
	cnt = 1;
	ans1.clear(); ans2.clear();
	memset(head, 0, sizeof(head));
	memset(Delet, false, sizeof(Delet));
	memset(deg, 0, sizeof(deg));
	memset(vis, false, sizeof(vis));
}

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void add(int u, int v){
	ed[++ cnt] = (Edge){head[u], v};
	head[u] = cnt;
}

void dfs(int u, int fa, int frm){
	vector<pair<int, int> > now;
	for(int i = head[u], v; i; i = ed[i].nxt)
		if((v = ed[i].to) != fa){
			dfs(v, u, i);
			if(!Delet[i]) now.push_back(make_pair(v, i));
		}
	if(now.size() <= 1) return;
	
	if(fa){
		Delet[frm] = Delet[frm ^ 1] = true;
		deg[fa] --, deg[u] --;
		ans1.push_back(make_pair(u, fa));
	}
	if(now.size() == 2) return;
	
	for(int i = 2; i < now.size(); i ++){
		int v = now[i].first;
		int frm = now[i].second;
		Delet[frm] = Delet[frm ^ 1] = true;
		deg[v] --, deg[u] --;
		ans1.push_back(make_pair(u, v));
	}
}

int Chain(int u){
	vis[u] = true;
	for(int i = head[u], v; i; i = ed[i].nxt)
		if(!vis[(v = ed[i].to)] && !Delet[i]) return Chain(v);
	return u;
}

int main(){
	T = read();
	while(T --){
		Clear();
		n = read();
		for(int i = 1; i < n; i ++){
			int u = read(), v = read();
			add(u, v), add(v, u);
			deg[u] ++, deg[v] ++;
		}
		
		dfs(1, 0, 0);
		for(int i = 1; i <= n; i ++)
			if(deg[i] == 0)
				ans2.push_back(make_pair(i, i));
			else if(deg[i] == 1 && !vis[i]){
				int to = Chain(i);
				ans2.push_back(make_pair(i, to));
			}
		
		printf("%d\n", ans1.size());
		for(int i = 0; i < ans1.size(); i ++)
			printf("%d %d %d %d\n", ans1[i].first, ans1[i].second, ans2[i].second, ans2[i + 1].first);
	}
	return 0;
}

E

给定 \(k\) 个数字,他们分别是 \(1,2,3,...,k\),对于数字 \(i\) 你有相同的 \(a_i\)​ 个。

定义一个 \(n\times n\) 的矩阵为美丽矩阵:

  • 这个 \(n\times n\) 的矩阵包含了你所拥有的所有数字。
  • 对于每个 \(2\times 2\) 的子矩阵,占用的格子不能超过 \(3\)

现在,你要构造一个最小的美丽矩阵。

奇特的构造题。

将格子分类为 \(x,y,z,0\) 四类,如图。

\(x\to red,y\to blue,z\to yellow\)

首先将众数调整至位置 \(1\)

二分出方阵大小 \(n\),条件为 \(x+y\geq a(1)\)\(x+y+z\geq m\),这显然是下限。

现在要证明存在合法的构造方案。

方案为从头开始(众数被调整至第一个),按照 \(x\to y\to z\) 的方法随意填充。

现在需要证明不存在不合法的情况,即红黄格子中没有相同的数。

  • 假设众数填满了红色格子,根据二分限制,众数最多再填满蓝色格子。

  • 假设众数没有填满红色格子,那么也不会存在数字 \(m\) 填了红色格子并填满蓝色格子。

    因为 \(y\geq x\),如果出现这种情况,与众数的定义相违背。

故得证明了构造的正确性。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 200010;
const int M = 1010;
int T, m, k, a[N], id[N];
int ans[M][M];
vector<pair<int, int> > x, y, z;

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Get(){
	int l = 0, r = 1000;
	while(l < r){
		int mid = (l + r) >> 1;
		if(mid * ((mid + 1) / 2) >= a[1] && mid * mid - (mid / 2) * (mid / 2) >= m) r = mid;
		else l = mid + 1;
	}
	return l;
}

int main(){
	T = read();
	while(T --){
		m = read(), k = read();
		int mx = 0;
		for(int i = 1; i <= k; i ++){
			a[i] = read(); id[i] = i;
			if(a[i] > a[mx]) mx = i;
		}
		swap(a[1], a[mx]), swap(id[1], id[mx]);
		int n = Get();
		printf("%d\n", n);
		
		x.clear(), y.clear(), z.clear();
		for(int i = 2; i <= n; i += 2)
			for(int j = 1; j <= n; j += 2)
				x.push_back(make_pair(i, j));
		for(int i = 1; i <= n; i += 2)
			for(int j = 1; j <= n; j += 2)
				y.push_back(make_pair(i, j));
		for(int i = 1; i <= n; i += 2)
			for(int j = 2; j <= n; j += 2)
				z.push_back(make_pair(i, j));
				
		for(int i = 1; i <= n; i ++)
			for(int j = 1; j <= n; j ++)
				ans[i][j] = 0;
		
		for(int i = 1; i <= k; i ++)
			for(int j = 1; j <= a[i]; j ++){
				if(x.size()){
					ans[x.back().first][x.back().second] = id[i];
					x.pop_back();
				}
				else if(y.size()){
					ans[y.back().first][y.back().second] = id[i];
					y.pop_back();
				}
				else{
					ans[z.back().first][z.back().second] = id[i];
					z.pop_back();
				}
			}
		for(int i = 1; i <= n; i ++){
			for(int j = 1; j <= n; j ++) printf("%d ", ans[i][j]);
			puts("");
		}
	}
}
posted @ 2021-05-26 11:18  LPF'sBlog  阅读(56)  评论(0编辑  收藏  举报