构造
构造
例题
AT_hitachi2020_c ThREE
给定一棵树,要求构造一个排列 \(P\),满足以下条件:
- 对树上的每一对点 \((i,j)\),如果这两个点之间的距离为 \(3\),则 \(p_i\times p_j\) 和 \(p_i+ p_j\) 中至少一个为 \(3\) 的倍数。
树上每一条边的长度为 \(3\)。
思路
先将倍数关系转化为取模:
将整体关系拆分为个体:
令 \(p_i\equiv x_i(\text{mod}~~~3)\),\(p_j\equiv x_j(\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\) 的边。
思路
显然,应该先分析异或的性质:
总结整理:
因为:
所以:
显然这就是一条满足条件的路径
将 \(1\) 作为根,将 \(1\rightarrow i\), \(1\rightarrow (i\oplus 1)\), \((i\oplus 1)\rightarrow i+N\) 连上即可
特殊情况:
- 当 \(i = 1\) 时,\(1\oplus i=0\),没有节点连接,此时将 \(1\rightarrow 2\),\(2\rightarrow (n+1)\) 连上符合条件
- 当 \(N\) 为偶数时,\(N\oplus1=N+1\),没有节点连接,此时连接路径 \(N\rightarrow (N−1)\rightarrow1\rightarrow2N\) 符合条件
- 当 \(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;
}

浙公网安备 33010602011771号