专栏文章
Steiner 三元系学习小记
算法·理论参与者 36已保存评论 39
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 39 条
- 当前快照
- 1 份
- 快照标识符
- @mhz5rnar
- 此快照首次捕获于
- 2025/11/15 01:55 4 个月前
- 此快照最后确认于
- 2025/11/29 05:24 3 个月前
前言
学习这玩意是因为补题 IOI2022 集训队互测 Day5T2。
题面是这样的:
现有 个人,你可以举办任意多次由三个人参加的聚会,现要求任意两个人都同时参加聚会恰好一次,试构造一组聚会方案。
可以说明,在给定的范围内一定有解。
一行一个正整数 ,保证 为 或 。
vp 时只写了前面的 十分就转头去冲 T3 了。补题时 wwc 的题解看起来实在让人太头大了,补题极其困难。
学长找到了一份名字叫 sts 的课件,于是我点开来看,是一份英文课件,直到看完这份课件,我终于会了这道题。觉得这道题很有意思,于是写了这篇博文。
本文的内容大部分来源于此英文课件,但由于我并不知道其具体出处,无法在此标注。
施泰纳三元系
一个 Steiner 三元系由一个集合 和一个三元组集合 组成。满足 中每对元素都在 中出现且出现次数为 。
比如 ;。
容易算出如果 那么 。
关于它的一个结论是, 存在当且仅当 。
因为包含 的三元组将 这些二元组两两配对,所以 只能是偶数,那么 只能是奇数。
当 时,。所以 不能为 。
当 时易证 。而这个结论剩下的部分需要我们通过构造证明。
拟群、拉丁方阵
拉丁方阵是一个 的方阵。它满足每行每列 每个数只出现一次。
例如 时的一种拉丁方阵为:
CPP123
231
312
求一个拉丁方阵非常容易,最常用的方法是对于每个数,随机选取方阵中一个空的格子,初始位置是 然后固定每次的移动是 。然后不断填充。
**拟群(quasi-group)**是一种代数系统。
拟群由一个集合 和一个二元运算 组成。
满足以下两个条件:
-
。
-
这两个方程只有唯一的 解。
举个例子 。
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1 | 2 | 0 |
| 1 | 0 | 1 | 2 |
| 2 | 2 | 0 | 1 |
幂等拟群是指满足 的拟群。
一个拟群是可交换的是指满足 的拟群。
看到这里也许你会发现,拟群的乘法表实际上就是一个拉丁方阵。
而幂等拟群可以用一个满足 的拉丁方阵表示。
而可交换这一性质可以由 来体现。
当 为奇数的时候,我们是可以构造一个可交换的幂等拟群的,具体形状如下:
CPP
1 3 2
3 2 1
2 1 3
CPP
1 4 2 5 3
4 2 5 3 1
2 5 3 1 4
5 3 1 4 2
3 1 4 2 5
可以发现我们只要选取的初始位置是主对角线上 的位置然后向右上方填充即可。
为偶数呢?很抱歉,我们构造不出来。原因很简单,一个可交换的幂等拟群中一个元素的出现次数必然是奇数次 ( 对角线上一次+矩阵中 次 )。
好了,至此我们可以得出一个 时的做法了。
关于 的构造方法
我们将 划分成三组,每组 个元素。
设 表示第 组的第 个元素。接下来我们构造如下两种三元组:
- 。
- 。
可以用如下一张图来解释:

这种构造的正确性证明:
首先来计算三元组总数,第一种三元组有 个,第二种有 个,加起来是 个。
接下来只要证明每对元素都在其中出现过即可。
(下文 和 均为模 意义下的值)
对于 和 两个元素,如果 那么显然出现过,如果 ,假设 根据 的解唯一,且易知 所以必定在第三种出现。
所以在代码实现中,我们只需要构造一个 可交换的幂等拟群的乘法表(拉丁方阵),然后构造如上的三元组即可。代码难度很小。
可交换的半幂等拟群
上文已经提到过, 为偶数的时候我们不能构造出可交换的幂等拟群。
但是,我们可以构造出半幂等拟群。
半幂等拟群指满足 的拟群。
CPP
1 3 2 4
3 2 4 1
2 4 1 3
4 1 3 2
CPP
1 4 2 5 3 6
4 2 5 3 6 1
2 5 3 6 1 4
5 3 6 1 4 2
3 6 1 4 2 5
6 1 4 2 5 3
构造方法是,对于前 个数在主对角线上一次构造,后 个数在主对角线下一斜构造。
关于 的构造方法
我们将前 个元素分成三组,每组 个元素。最后一个元素 不参与分组。
设 表示第 组的第 个元素。接下来我们构造如下三种三元组:
-
,注意这里是 。
-
。
-
。
同样来分析下正确性:
第一种三元组个数为 个,第二种为 个,第三种为 个,加起来是 个。
接下来只要证明每对元素都在其中出现过即可。
(下文 和 均为模 意义下的值)
当其中一个元素是 时,显然在第二种出现过。
对于 和 两个元素,如果 ,当 时在第一种中出现过,否则的话根据上面 Part 3 的分析方法总会在第三种出现。
如果 ,那么如果 ,他们会在第三种出现。如果 ,假设 ,那么因为 的 一定为 所以他并不会在第三种三元组出现而是在第二种中出现。
其他情况均可类似证明在第三种出现过。
所以在代码实现中,我们只需要构造一个 可交换的半幂等拟群的乘法表(拉丁方阵),然后构造如上的三元组即可。代码难度同样很小。
代码实现和个人想法
这题在构造时需要充分利用拟群的定义才能得出思路,包括 的部分分也需要与拟群类似的思想结合分治才能构造出来。个人感觉是一道很好玩的题。
C#include<bits/stdc++.h>
#define poly vector<int>
#define IOS ios::sync_with_stdio(false)
#define ll long long
#define mp make_pair
#define mt make_tuple
#define pa pair < int,int >
#define fi first
#define se second
#define inf 1e18
#define mod 998244353
//#define int ll
#define N 3005
using namespace std;
inline char gc(){static char buf[1000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;}
#define gc getchar
inline ll read(){char c=gc();ll su=0,f=1;for (;c<'0'||c>'9';c=gc()) if (c=='-') f=-1;for (;c>='0'&&c<='9';c=gc()) su=su*10+c-'0';return su*f;}
inline void write(ll x){if (x<0){putchar('-');write(-x);return;}if (x>=10) write(x/10);putchar(x%10+'0');}
inline void writesp(ll x){write(x),putchar(' ');}
inline void writeln(ll x){write(x);putchar('\n');}
int id[N][5];
int p;
int a[N][N];
int n;
vector<tuple<int,int,int>>ans;
void Init(int x)//构造可交换的幂等拟群
{
for (int i=1;i<=x;i++)
for (int j=1;j<=3;j++)
id[i][j]=(i-1)*3+j;
for (int i=1;i<=x;i++)
{
int nowx=i,nowy=i;
while (!a[nowx][nowy])
{
a[nowx][nowy]=i;
nowx--;
nowy++;
if (nowx==0) nowx=x;
if (nowy==x+1) nowy=1;
}
}
}
void Init1(int x)//构造可交换的半幂等拟群
{
for (int i=1;i<=x;i++)
for (int j=1;j<=3;j++)
id[i][j]=(i-1)*3+j;
p=n;
for (int i=1;i<=x/2;i++)
{
int nowx=i,nowy=i;
while (!a[nowx][nowy])
{
a[nowx][nowy]=i;
nowx--;
nowy++;
if (nowx==0) nowx=x;
if (nowy==x+1) nowy=1;
}
}
for (int i=x/2+1;i<=x;i++)
{
int nowx=i-x/2+1,nowy=i-x/2;
while (!a[nowx][nowy])
{
a[nowx][nowy]=i;
nowx--;
nowy++;
if (nowx==0) nowx=x;
if (nowy==x+1) nowy=1;
}
}
}
void outp()
{
for (auto u:ans) writesp(get<0>(u)),writesp(get<1>(u)),writeln(get<2>(u));
}
void BellaKira()
{
n=read();
if (n%6==3)
{
Init(n/3);
for (int i=1;i<=n/3;i++)
for (int j=i;j<=n/3;j++)
{
if (i==j)
ans.push_back(mt(id[i][1],id[i][2],id[i][3]));
else
{
for (int k=1;k<=3;k++)
ans.push_back(mt(id[i][k],id[j][k],id[a[i][j]][(k)%3+1]));
}
}
outp();
} else
{
Init1((n-1)/3);
for (int i=1;i<=n/3;i++)
{
for (int j=i;j<=n/3;j++)
{
if (i==j&&i<=n/6)
ans.push_back(mt(id[i][1],id[i][2],id[i][3]));
else
if (i!=j)
{
for (int k=1;k<=3;k++)
ans.push_back(mt(id[i][k],id[j][k],id[a[i][j]][(k)%3+1]));
}
}
if (i<=n/6)
{
for (int k=1;k<=3;k++)
ans.push_back(mt(p,id[i][k],id[n/6+i][(k-1+3-1)%3+1]));
}
}
outp();
}
}
signed main()
{
int T=1;
while (T--)
{
BellaKira();
}
}
/*
*/
相关推荐
评论
共 39 条评论,欢迎与作者交流。
正在加载评论...