构造

构造

例题

AT_hitachi2020_c ThREE

给定一棵树,要求构造一个排列 \(P\),满足以下条件:

  • 对树上的每一对点 \((i,j)\),如果这两个点之间的距离为 \(3\),则 \(p_i\times p_j\)\(p_i+ p_j\)至少一个\(3\) 的倍数。

树上每一条边的长度为 \(3\)

思路

先将倍数关系转化为取模:

\[(p_i\times p_j) \equiv0(\text{mod}~~~ 3)\\ (p_i+ p_j) \equiv0(\text{mod}~~~ 3) \]

将整体关系拆分为个体:

\(p_i\equiv x_i(\text{mod}~~~3)\)\(p_j\equiv x_j(\text{mod}~~~3)\)

\[(x_i\times x_j) \equiv0(\text{mod}~~~ 3)\\ (x_i+ x_j) \equiv0(\text{mod}~~~ 3) \]

显然,\(x\) 只有 \(3\) 种情况:\(\{0,1,2\}\)

则可能导致条件不成立的情况只有 \(2\) 种:\(x_i\)\(x_j\) 同时为 \(1\)\(2\)

那怎么确定距离为 \(3\) 的点对?

可以将整棵树染成两种颜色,相邻的节点为不同的颜色。显然颜色不同的节点距离一定为奇数\(3\) 是奇数(这是一句废话)。

这时只需要在不同颜色的节点填余数不同的数就可以了,\(0\) 则在 \(1\)\(2\) 后将没有填的节点补齐。

但实际情况还需分类讨论(令 \(cnt0\)白点的数量,\(cnt1\)黑点的数量):

  • \(cnt0,cnt1\ge \lfloor\cfrac{n}{3} \rfloor\) 时,此时所有黑白节点都能分配到余 \(1\)\(2\) 的数字,直接按照上面的方法填
  • \(cnt0\ge \lfloor\cfrac{n}{3} \rfloor\) 时,将所有余 \(0\) 的数字分配给黑点,其它全部给白点(由于 \(0\times x\equiv0(\text{mod}~~~3)\),所以依然成立)
  • \(cnt1\ge \lfloor\cfrac{n}{3} \rfloor\) 时,同理,将所有余 \(0\) 的数字分配给白点,其它全部给黑点

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5;
int n;
vector<int> G[maxn + 5];
int p0 = 3,p1 = 1,p2 = 2; // 指向 模3后余0,1,2 的数
int ans[maxn + 5]; // 答案
int color[maxn + 5]; // 节点颜色1/2
int col1,col2; // 黑白的总数
void paint(int u,int fa,int c) { // 染色
	color[u] = c;
	for (auto v : G[u]) {
		if (v == fa) continue;
		paint(v,u,c == 1 ? 2 : 1);
	}
}
int main() {
	scanf("%d",&n);
	for (int i = 1;i <= n - 1;i ++) {
		int u,v; scanf("%d %d",&u,&v);
		G[u].push_back(v); G[v].push_back(u);
	}
	paint(1,0,1);
	for (int i = 1;i <= n;i ++) {
		if (color[i] == 1) col1 ++;
		else col2 ++;
	}
	if (col1 > n / 3 && col2 > n / 3) {
		for (int i = 1;i <= n;i ++) {
			if (color[i] == 1) {
				if (p1 <= n) {
					ans[i] = p1;
					p1 += 3;
				} else {
					ans[i] = p0;
					p0 += 3;
				}
			} else {
				if (p2 <= n) {
					ans[i] = p2;
					p2 += 3;
				} else {
					ans[i] = p0;
					p0 += 3;
				}
			}
		}
	} else if (col1 > n / 3) {
		for (int i = 1;i <= n;i ++) {
			if (color[i] == 1) {
				if (p1 <= n) {
					ans[i] = p1;
					p1 += 3;
				} else if (p2 <= n) {
					ans[i] = p2;
					p2 += 3;
				} else if (p0 <= n) {
					ans[i] = p0;
					p0 += 3;
				}
			} else {
				ans[i] = p0;
				p0 += 3;
			}
		}
	} else {
		for (int i = 1;i <= n;i ++) {
			if (color[i] == 0) {
				if (p1 <= n) {
					ans[i] = p1;
					p1 += 3;
				} else if (p2 <= n) {
					ans[i] = p2;
					p2 += 3;
				} else if (p0 <= n) {
					ans[i] = p0;
					p0 += 3;
				}
			} else {
				ans[i] = p0;
				p0 += 3;
			}
		}
	}
	for (int i = 1;i <= n;i ++) printf("%d ",ans[i]);
	return 0;
}

AT_agc035_c [AGC035C] Skolem XOR Tree

给定一个正整数 \(N\)\(1\le N\le10^5\))。

试判断,是否存在这样一棵节点数为 \(2N\) 的树,满足:

  • \(i\in [1,n]\) ,第 \(i\) 号节点和第 \(i+N\) 号节点的权值均为 \(i\)
  • \(i\) 号节点到第 \(i+N\) 号节点路径上的点的点权异或和恰为 \(i\)

若不存在这样的树,请输出一行 No

否则先输出一行 Yes,然后再输出 \(2N-1\) 行,每行两个正整数 \(u\)\(v\) 描述树上的一条连接 \(u\)\(v\) 的边。

思路

显然,应该先分析异或的性质:

\[x\oplus x = 0\\ 1\oplus (x\oplus 1) = x\\ x\oplus 0=x \]

总结整理:

\[x\oplus1\oplus(x\oplus1)\oplus x=x \]

因为:

\[val_i=val_{i+N} \]

所以:

\[val_i\oplus1\oplus(val_i\oplus1)\oplus val_{i+N}=i \]

显然这就是一条满足条件的路径

\(1\) 作为根,将 \(1\rightarrow i\)\(1\rightarrow (i\oplus 1)\)\((i\oplus 1)\rightarrow i+N\) 连上即可

特殊情况:

  1. \(i = 1\) 时,\(1\oplus i=0\),没有节点连接,此时将 \(1\rightarrow 2\)\(2\rightarrow (n+1)\) 连上符合条件
  2. \(N\) 为偶数时,\(N\oplus1=N+1\),没有节点连接,此时连接路径 \(N\rightarrow (N−1)\rightarrow1\rightarrow2N\) 符合条件
  3. \(N\)\(2^k\) 时,由于 \(1\oplus2\cdots\oplus 2^k-1=0\),无解

代码

#include <bits/stdc++.h>
#define lowbit(x) (x & (-x))
using namespace std;
int n;
int main() {
	scanf("%d",&n);
	if (lowbit(n) == n) { // n = 2^k
		printf("No");
		return 0;
	}
	printf("Yes\n");
	for (int i = 2;i <= n;i ++) {
		if (i ^ 1 > i && i ^ 1 <= n) printf("%d %d\n",i,i ^ 1);
	}
	printf("1 2\n");
	for (int  i = 1;i <= n;i ++) {
		if (i ^ 1 == 0) printf("%d 2\n",i + n);
		else printf("%d %d\n",i + n,i ^ 1);
	}
	return 0;
}

AT_hitachi2020_e Odd Sum Rectangles

思路

有一个 \(2^n-1\)\(2^m-1\) 列的矩阵,矩阵每一个位置上有一个数,这个数只能是 \(0\)\(1\)

构造这个矩阵,使得它的所有连续子矩阵中尽可能多的子矩阵中数的和为奇数,若有多种构造方式满足这一点,输出任意一个方案。

作矩阵的异或前缀和矩阵,第一行第一列均为 \(0\),得到一个 \(2^n\times2^m\) 的矩阵。这样就把问题转化为了在异或前缀和矩阵中,四个顶点异或和为奇数的矩阵个数。

这时,任选两行两列,通过 \(4\) 个交点就可以确定一个子矩阵的异或和(区间问题转前缀问题

假设任选两行为 \(x\)\(y\),设两行相同位置相同的数量为 \(u\),不同为 \(v\),则显然 \(u + v = 2^m\)

显然,一个相同与一个不同可以组成一个顶点异或和为奇数的矩阵,此时数量为 \(uv\)

通过均值不等式可以知道当 \(u=v\) 时,\(uv\) 有最大值 \(2^{2m-2}\)

所以只需要构造两行之间有一半不同的异或前缀和矩阵,再推导出原矩阵即可

异或前缀和矩阵满足哈达玛矩阵的一切性质

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 10;
int n,m;
int sum[(1 << maxn) + 5][(1 << maxn) + 5];
int a[(1 << maxn) + 5][(1 << maxn) + 5];
void init(int k) { // 构造前缀异或和矩阵
	if (k == 0) {
		sum[0][0] = 0;
		return ;
	}
	init(k - 1);
	for (int i = 0;i <= (1 << (k - 1)) - 1;i ++) {
		for (int j = 0;j <= (1 << (k - 1)) - 1;j ++) {
			sum[i + (1 << (k - 1))][j] = sum[i][j];
			sum[i][j + (1 << (k - 1))] = sum[i][j];
			sum[i + (1 << (k - 1))][j + (1 << (k - 1))] = 1 - sum[i][j];
		}
	}
//	printf("%d X %d\n",1 << k,1 << k);
//	for (int i = 0;i <= (1 << k) - 1;i ++) {
//		for (int j = 0;j <= (1 << k) - 1;j ++) printf("%d ",sum[i][j]);
//		printf("\n");
//	}
}
int main() {
	scanf("%d %d",&n,&m);
	init(max(n,m));
//	for (int i = 1;i <= 1 << n;i ++) {
//		for (int j = 1;j <= 1 << n;j ++) printf("%d ",sum[i][j]);
//		printf("\n");
//	}
	for (int i = 1;i <= (1 << n) - 1;i ++) 
		for (int j = 1;j <= (1 << m) - 1;j ++)
	    a[i][j] = sum[i - 1][j] ^ sum[i][j - 1] ^ sum[i - 1][j - 1] ^ sum[i][j]; // 倒推原矩阵
  for (int i = 1;i <= (1 << n) - 1;i ++) {
		for (int j = 1;j <= (1 << m) - 1;j ++) printf("%d ",a[i][j]);
		printf("\n");
	}
	return 0;
}

练习题

posted @ 2026-08-26 01:06  Denia-kawaii  阅读(4)  评论(0)    收藏  举报