namespace_std 杂题选讲
CF1458C Latin Square
将每个数表示成三元组 \((i,j,a[i][j])\) ,UDLR
相当于给前两维加一或减一,IC
相当于交换某两维。
操作是对整体进行操作的,那么直接记录操作对每个位置的影响即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T;
int n,m,a[1005][1005],loc[3],delta[3],ans[1005][1005];
char s[100005];
inline void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
}
for(int i=0;i<3;i++)loc[i]=i;
for(int i=0;i<3;i++)delta[i]=0;
scanf("%s",s+1);
for(int i=1;i<=m;i++){
if(s[i]=='U')delta[loc[0]]--;
if(s[i]=='D')delta[loc[0]]++;
if(s[i]=='L')delta[loc[1]]--;
if(s[i]=='R')delta[loc[1]]++;
if(s[i]=='I')swap(loc[1],loc[2]);
if(s[i]=='C')swap(loc[0],loc[2]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int tmp[3]={i,j,a[i][j]},res[3];
for(int t=0;t<3;t++)tmp[t]=((tmp[t]+delta[t]-1)%n+n)%n+1;
for(int t=0;t<3;t++)res[t]=tmp[loc[t]];
ans[res[0]][res[1]]=res[2];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",ans[i][j]);
}
puts("");
}
puts("");
}
int main(){
scanf("%d",&T);
while(T--)solve();
return 0;
}
2021 EC Final C. Random Shuffle
可以通过输入的 \(a_i\) 还原每个 \(i\in[1,n],\rm rand_i\bmod\ i\),对于 \(n>50\) 的情况,先把最后 \(n−50\) 轮进行逆操作,即在计算答案的过程中只利用前 \(50\) 轮交换中每个 \(\rm rand_i\bmod i\) 提供的信息。
将 \(x\) 写成二进制 \(\overline{x_{63}\dots x_{0}}\) ,可以模拟题中异或操作得到第 \(i\) 次 \(\rm rand\) 返回的数值 \(\rm rand_i\) 的每个二进制位是由哪些 \(x_i\) 异或得到(一开始设 X[i]=1<<i
,每次调用函数等价于 X[i]^=X[i-13],X[i]^=X[i+7],X[i]^=X[i-17]
)
注意当 i 是偶数时,\(\rm rand_i\bmod i\) 和 \(\rm rand_i\) 的二进制表示中最后一位一致,那么通过上面维护出来的最后一位对应哪些 \(x_i\) 异或起来得到一个方程,类似的,如果 \(x≡0\bmod2^k\) 那么至少能得到 \(k\) 个方程。
但是有主元的方程只有 \(\frac{50}{2}+\frac{50}{4}+\frac{50}{8}+\frac{50}{16}+\frac{50}{32}=47\) 个,不过剩下的可以暴力枚举,所以复杂度约为 \(Θ((64-n)×2^{17})\) 。
使用线性基把这些方程消成上三角,注意每个有主元的元素的 \(x_i\) 需要依赖其前面确定的 \(x_i\) 的数值。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct Equation{
unsigned long long vec;bool res;
Equation():vec(0),res(0){}
Equation(unsigned long long V,bool R):vec(V),res(R){}
};
unsigned long long fre;
Equation equ[64];
unsigned long long mat[100005][64];
int perm[100005],pos[100005];
int A[100005];
int n;
namespace Validator{
unsigned long long seed;
int tmp[100005];
inline unsigned long long Rand(){
seed^=seed<<13;
seed^=seed>>7;
seed^=seed<<17;
return seed;
}
inline bool Validate(const unsigned long long&X){
seed=X;
for(int i=1;i<=n;i++){
tmp[i]=i;
swap(tmp[i],tmp[Rand()%i+1]);
}
for(int i=1;i<=n;i++)if(tmp[i]!=A[i])return 0;
return 1;
}
}
inline Equation operator^(const Equation&a,const Equation&b){
return Equation(a.vec^b.vec,a.res^b.res);
}
inline Equation&operator^=(Equation&a,const Equation&b){
return a=a^b;
}
inline void AddEquation(Equation nw){
for(int i=0;i<64;i++){
if(nw.vec>>i&1){
if(!equ[i].vec){
equ[i]=nw;break;
}
nw^=equ[i];
}
}
}
inline unsigned long long Generate(const unsigned long long&freState){
unsigned long long ret=freState;
for(int k=0;k<64;k++)if(equ[k].vec){
int idx=__builtin_ctzll(equ[k].vec);
ret^=(1llu*(__builtin_parityll(equ[k].vec&freState)^equ[k].res))<<idx;
}
return ret;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&A[i]);
perm[i]=A[i],pos[perm[i]]=i;
}
for(int i=0;i<64;i++)mat[0][i]=1llu<<i;
for(int i=1;i<=n;i++){
for(int j=0;j<64;j++){
mat[i][j]=mat[i-1][j];
mat[i][j]^=mat[i][j]<<13;
mat[i][j]^=mat[i][j]>>7;
mat[i][j]^=mat[i][j]<<17;
}
}
for(int i=n;i>=1;i--){
int r=pos[i]-1;
swap(pos[i],pos[perm[i]]);
swap(perm[pos[perm[i]]],perm[i]);
int t=__builtin_ctz(i);
for(int j=0;j<t;j++){
Equation tmp;tmp.res=r>>j&1;
for(int k=0;k<64;k++)
tmp.vec|=(mat[i][k]>>j&1)<<k;
AddEquation(tmp);
}
}
fre=64==64?-1:(1llu<<64)-1;
for(int k=0;k<64;k++)if(equ[k].vec){
int idx=__builtin_ctzll(equ[k].vec);
fre^=1llu<<idx;
for(int j=0;j<64;j++)
if(j^k&&equ[j].vec>>idx&1)equ[j]^=equ[k];
}
bool ever=0;unsigned long long ans;
for(unsigned long long s=fre;s;s=(s-1)&fre){
ans=Generate(s);
if(Validator::Validate(ans)){
ever=1;break;
}
}
if(!ever){
ans=Generate(0);
if(Validator::Validate(ans))ever=1;
}
printf("%llu\n",ans);
return 0;
}
[THUPC2021] 混乱邪恶
以“简洁的题面”和“无私馈赠的样例”为基底建立坐标系,六个风格对应着六个向量 \((1,0),(0,-1),(-1,-1),(-1,0),(0,1),(1,1)\)。
考虑暴力 \(dp\) ,令 \(dp[t][i][j][x][y]\) 表示处理了前 \(t\) 个 \(\text{idea}\) ,\(L=i,G=j\) ,当前位置是 \((x,y)\) ,是否可行,可以用 \(\text{bitset}\) 优化做到 \(O(\dfrac{n^3p^2}{\omega})\) ,卡不过去。
考虑如何优化,但是这样的背包已经是最优了,也就是说背包是不可能再优化了,只能考虑从其它地方优化。
考虑非常经典的随机游走问题,它告诉我们在二维平面上每次随机选一个方向走 \(1\) 的单位长度,走 \(n\) 步期望的距离不会超过 \(\sqrt n\) 级别,而这道题我们选择的方案也可以当成是随机在 \(6\) 种当中选 \(1\) 种出来。
所以我们只需将输入的 \(\text{idea}\) 随机化,并将 \(i,j\) 这两维只开到 \(\sqrt n\) ,这样复杂度就降为 \(O(\dfrac{n^2p^2}{\omega})\) 了。(事实上要开到 \(2\sqrt n\) ,\(\sqrt n\) 会被卡掉)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,p;
int Lu01[105],Gu01[105],Ld10[105],Gd10[105],Ld11[105],Gd11[105];
int Ld01[105],Gd01[105],Lu10[105],Gu10[105],Lu11[105],Gu11[105];
bitset<45> vis[105][105][105][45];
int main(){
srand(time(0));
scanf("%d%d",&n,&p);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d%d%d",&Lu01[i],&Gu01[i],&Ld10[i],&Gd10[i],&Ld11[i],&Gd11[i]);
scanf("%d%d%d%d%d%d",&Ld01[i],&Gd01[i],&Lu10[i],&Gu10[i],&Lu11[i],&Gu11[i]);
}
for(int i=1;i<=n;i++){
int x=rand()%n+1;
swap(Lu01[i],Lu01[x]);swap(Ld01[i],Ld01[x]);swap(Gu01[i],Gu01[x]);swap(Gd01[i],Gd01[x]);
swap(Lu10[i],Lu10[x]);swap(Ld10[i],Ld10[x]);swap(Gu10[i],Gu10[x]);swap(Gd10[i],Gd10[x]);
swap(Lu11[i],Lu11[x]);swap(Ld11[i],Ld11[x]);swap(Gu11[i],Gu11[x]);swap(Gd11[i],Gd11[x]);
}
int lim=min(20,n);
vis[0][0][0][lim][lim]=1;
for(int t=0;t<n;t++){
for(int i=0;i<p;i++){
for(int j=0;j<p;j++){
int deltaL=0,deltaG=0,deltax=0,deltay=0;
deltaL=Lu01[t+1],deltaG=Gu01[t+1],deltay=1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
if(deltay>0)vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]<<1);
else vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]>>1);
}
deltaL=Ld01[t+1],deltaG=Gd01[t+1],deltay=-1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
if(deltay>0)vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]<<1);
else vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]>>1);
}
deltaL=Lu10[t+1],deltaG=Gu10[t+1],deltax=1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
vis[t+1][ui][uj][ux]|=vis[t][i][j][x];
}
deltaL=Ld10[t+1],deltaG=Gd10[t+1],deltax=-1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
vis[t+1][ui][uj][ux]|=vis[t][i][j][x];
}
deltaL=Lu11[t+1],deltaG=Gu11[t+1],deltax=1,deltay=1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
if(deltay>0)vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]<<1);
else vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]>>1);
}
deltaL=Ld11[t+1],deltaG=Gd11[t+1],deltax=-1,deltay=-1;
for(int x=0;x<=2*lim;x++){
int ux=x+deltax,ui=(i+deltaL)%p,uj=(j+deltaG)%p;
if(ux<0||ux>2*lim)continue;
if(deltay>0)vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]<<1);
else vis[t+1][ui][uj][ux]|=(vis[t][i][j][x]>>1);
}
}
}
}
int L,G;scanf("%d%d",&L,&G);
puts(vis[n][L][G][lim][lim]?"Chaotic Evil":"Not a true problem setter");
return 0;
}
[JOISC2022] 制作团子 3
从左向右一次询问区间 \([1,i]\),第一个返回 \(1\) 的位置 \(x\) 一定是该颜色的第一次出现。
将 \(x\) 加入当前集合 \(ans\),然后从后向前扫,如果当前扫到位置 \(i\),区间 \([1,i]\) 加上 \(ans\) 集合里的数不能组成一组,则把 \(i+1\) 加入 \(ans\) 集合。
扫一个来回后 \(ans\) 集合就是一个合法组。直接扫需要询问 \(nm^2\) 次,显然是无法通过的。
我们可以优化一下,向右扫的过程我们可以直接二分答案。向左扫虽然也可以二分,但是需要二分的次数太多反而是负优化,我们类似循环展开每次向前跳 \(3\) 个位置即可。这样期望的询问次数 \(m\log nm+\dfrac{nm^2}{6}\) 勉强可以通过。
但是如果所有球是有序的会被卡成 \(\dfrac{nm^2}{3}\),所以我们开始前对所有标号随机打乱,这样无论数据如何对于我们都是随机均匀的。
点击查看代码
#include<bits/stdc++.h>
#include"dango3.h"
using namespace std;
int p[10005],a[10005],b[10005],t;
vector<int> ans;
inline int ask(int x){
vector<int> u=ans;
for(int i=1;i<=x;i++)u.push_back(p[a[i]]);
return Query(u);
}
void Solve(int n,int m){
for(int i=1;i<=n*m;i++)p[i]=i,a[i]=i;
random_shuffle(p+1,p+n*m+1);
t=n*m;
for(int i=1;i<=m;i++){
if(i==m){
for(int j=1;j<=n;j++)ans.push_back(p[a[j]]);
Answer(ans);return ;
}
int l=n,r=t,w=0;
while(l<=r){
int mid=(l+r)>>1;
if(ask(mid))w=mid,r=mid-1;
else l=mid+1;
}
ans.push_back(p[a[w]]),a[w--]=0;
while(ans.size()<n){
if(w<5){
if(!ask(w-1)){
ans.push_back(p[a[w]]),a[w]=0;
}w--;
}
else{
if(ask(w-3))w-=3;
else{
if(!ask(w-1))ans.push_back(p[a[w]]),a[w]=0,w--;
else if(!ask(w-2))ans.push_back(p[a[w-1]]),a[w-1]=0,w-=2;
else ans.push_back(p[a[w-2]]),a[w-2]=0,w-=3;
}
}
}
Answer(ans);ans.clear();
int T=0;
for(int j=1;j<=t;j++)if(a[j])b[++T]=a[j];
t=T;for(int j=1;j<=t;j++)a[j]=b[j];
}
}
2022 集训队互测 Were You Last
交互题,你需要实现一个函数 bool WereYouLast(n,m)
,此函数会被调用 \(n\) 次,你需要在调用第 \(n\) 次时返回 \(true\)。
你不能使用任何全局变量等可以在多次调用之间交换信息的途径。交互器给你提供了 \(m\) 个 \(0/1\) 布尔型变量,你可以对其进行读取或修改。
数据保证 \(m = 10^5\), \(1 ≤ n ≤ 2^{26}\),且 \(n\) 为 \(2\) 的幂次,你需要保证每次函数调用的时候的读取和修改的位置数(对一个位置读取并修改算作一次)不超过 \(6\) 次。
\(n\) 为 \(2\) 的幂次,这启发我们去针对这一性质进行构造。
为什么需要 \(6\) 次操作?或者说,这 \(6\) 次操作如何拆分?
考虑一个经典的模型:
有 \(n + 1\) 个点(编号 \(0, 1, . . . , n\)),点有点权,初始为 \(0\),你初始在 \(0\) 号点;
每次操作中:若当前点点权为 \(0\),那么移动到 \(0\) 号点,否则移动到当前点编号加 \(1\) 的点。
移动后,将当前点点权翻转。
不难证明,我们会在 \(2^n\) 次操作后第一次到达点 \(n\)。
于是,我们用前 \(n + 1\) 个位置模拟自动机状态,单独留出 \(5\) 个位置模拟当前在自动机上的位置(这 \(5\) 个位置每次修改都要读写,自动机上面只读写一位),就可以在单次操作只读写 \(6\) 个位置的情况下完成任务。
CF1500D Tiles for Bathroom
求 \(f[i][j][k]\) 表示 \((i, j)\) 为右下角,第 \(k\) 远的颜色,\(g[i][j][k]\) 表示 \((i, j)\) 为右下角,最近的出现位置第 \(k\) 远的颜色的距离。这里距离定义为切比雪夫距离。
我们计算的是 \(g[i][j][q+1] − 1\) 的和,考虑如何高速递推 \(f\) 和 \(g\) 。
不难发现 \(f[i][j][k]\) 和 \(g[i][j][k]\) 可以通过 \(f[i−1][j−1][k]\)、\(g[i−1][j−1][k]\) 和 \((i, j)\) 左侧的前 \(q + 1\) 个颜色及其距离、\((i, j)\) 上方的前 \(q + 1\) 个颜色及其距离这三部分信息归并得出。
复杂度 \(O(n^2q \log q)\) 或 \(O(n^2q)\),取决于归并的实现,但直接排序和归并都能通过。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
int a[1505][1505],ans[1505];
bool vis[4000005];
vector<pair<short,int> > dp[1505][1505];
vector<short> L[1505][1505],R[1505][1505];
inline void merge(vector<pair<short,int> > &res,vector<pair<short,int> > x,vector<pair<short,int> > y,vector<pair<short,int> > z){
x.push_back(make_pair(n+1,0));reverse(x.begin(),x.end());
y.push_back(make_pair(n+1,0));reverse(y.begin(),y.end());
z.push_back(make_pair(n+1,0));reverse(z.begin(),z.end());
while(res.size()<q){
while(vis[x.back().second])x.pop_back();
while(vis[y.back().second])y.pop_back();
while(vis[z.back().second])z.pop_back();
if(x.size()==1&&y.size()==1&&z.size()==1)break;
if(x.back()<=min(y.back(),z.back()))res.push_back(x.back()),x.pop_back();
else if(y.back()<=min(x.back(),z.back()))res.push_back(y.back()),y.pop_back();
else res.push_back(z.back()),z.pop_back();vis[res.back().second]=1;
}
}
int main(){
scanf("%d%d",&n,&q);q++;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
}
for(int i=n;i;i--){
for(int j=n;j;j--){
dp[i][j].push_back(make_pair(-1,a[i][j]));
L[i][j].push_back(0);
R[i][j].push_back(0);
for(auto it:L[i][j+1]){
if(L[i][j].size()==q)break;
if(a[i][j+it+1]==a[i][j])continue;
L[i][j].push_back(it+1);
}
for(auto it:R[i+1][j]){
if(R[i][j].size()==q)break;
if(a[i+it+1][j]==a[i][j])continue;
R[i][j].push_back(it+1);
}
vector<pair<short,int> > tmp1,tmp2;
for(auto it:L[i][j+1])tmp1.push_back(make_pair(it,a[i][j+it+1]));
for(auto it:R[i+1][j])tmp2.push_back(make_pair(it,a[i+it+1][j]));
vis[a[i][j]]=1;merge(dp[i][j],dp[i+1][j+1],tmp1,tmp2);
for(auto it:dp[i][j])vis[it.second]=0;
for(auto &it:dp[i][j])it.first++;
if(dp[i][j].size()<q)ans[n-max(i,j)+1]++;
else ans[min(n-max(i,j)+1,(int)dp[i][j].back().first)]++;
L[i][j+1]=vector<short>();
R[i+1][j]=vector<short>();
dp[i+1][j+1].clear();
if(i==1||j==1)dp[i][j]=vector<pair<short,int> >();
if(i==1)R[i][j]=vector<short>();
if(j==1)L[i][j]=vector<short>();
}
}
for(int i=n;i;i--)ans[i]+=ans[i+1];
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
return 0;
}
lcm
题意简述:给定 \(n≤10^4\) 的序列 \(a(a_i≤10^{18})\),求一个字典序最小的序列 \(b\),满足 \(∀i∈[1,n],b_i|a_i\)
有一种可能的思维路径:不能分解质因数就不能统筹全局,而和统筹全局相对的就是增量法,所以倒序处理即可
从 \(i+1\) 开始的后缀推到 \(i\) 开始的后缀需要先找到 \(a_i\) 包含的质因子中指数是后缀最大值的那些,那么令 \(t=a_i\) 并遍历所有 \(j\) 使 \(t←\dfrac{t}{\gcd(ai,bj)}\)
\(t\) 中剩余的质因子就是 \(b_i\) 应该包含的质因子,将其指数调整成 \(a_i\) 唯一分解中该因子指数即可,这个过程可以通过令 \(b_i←b_i×\gcd(a_i,t),a_i←a_i\gcd(a_i,t)\) 来实现,也可以直接求 \(\gcd(v^{\log a_i},a_i)\) (每个因子指数最大是 \(\log a_i\),但是精度不知道能不能保证)赋给 \(b_i\)
这个过程复杂度是 \(Θ(n^2\log n)\) ,但是 \(\prod_{j<i}b_j\bmod a_i\) 的值就包含了所有 \(b_i\) 中包含的质因子,将其当做 \(t\) 也可以求出来合法的 \(b_j\)
求出 \(b_i\) 后需要将后缀中有 \(b_i\) 中含有的因子消去,通过二分 \(\gcd(\prod^j_{k=i+1}b_k\bmod b_i)\) 变化的所有 \(j\) 得到需要修改的位置,显然位置只有 \(\log V\) 个,总复杂度也是合理的。
『MdOI R2』Resurrection
显然生成的图 \(G\) 是一棵树。
\(G\) 有什么性质?
引理:\(G\) 能被生成,当且仅当 \(G\) 中的所有点 \(p\) 的父亲 \(f_p\) 都是 \(T\) 上的祖先,且不存在两条链 \((x, f_x)\) 和 \((y, f_y)\) 相交且不包含。
证明:必要性显然。
考虑如何证明任意一棵满足上述要求的树 \(G_0\) 都能被构造。
首先如果 \(G_0\) 只有一个节点,结论显然成立。因此我们只考虑根至少有一个儿子的情况。
将 \(n\) 号节点在 \(G_0\) 上的所有子节点中,在 \(T\) 上深度最大的那一个,记作 \(c\) 。
我们删除边 \((c, fa_c)\),由于 \(c\) 是最深的在 \(G_0\) 上直接连接 \(n\) 的节点,因此 \(c\) 在 \(T\) 上的子树内不会有点连接到 \(c\) 的祖先(首先链不能相交,因此不会连接到除 \(n\) 以外的点,但是又由于 \(c\) 是最深的,因此也不能连接 \(n\)),于是 \(c\) 所在的子树和树的其余部分变为两个独立的子问题。
根据数学归纳法,任意大小的树 \(G_0\) 可以被上述递归方法构造出来。
于是,令 \(f_{i,j}\) 表示若 \(i\) 点选择完父亲后,\(i\) 的祖先中有 \(j\) 个点可以被儿子选择(不产生冲突),\(i\) 子树内所有点选择祖先的方案数。
由于 \(i\) 的子树之间独立,每个子树可以向 \(i\) 以及 \(i\) 祖先内的任意 \(j\) 个点之一选择父亲,因此有转移式:
使用前缀和优化即可做到 \(O(n^2)\) 。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
int ver[6005],ne[6005],head[3005],cnt;
inline void link(int x,int y){
ver[++cnt]=y;
ne[cnt]=head[x];
head[x]=cnt;
}
int dp[3005][3005],dep[3005];
const int md=998244353;
void dfs(int x,int fi){
dep[x]=dep[fi]+1;
for(int i=1;i<=dep[x];i++)dp[x][i]=1;
for(int i=head[x];i;i=ne[i]){
int u=ver[i];
if(u==fi)continue;
dfs(u,x);
int sum=0;
for(int j=1;j<=dep[x];j++){
sum=(sum+dp[u][j+1])%md;
dp[x][j]=1ll*dp[x][j]*sum%md;
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
int x,y;scanf("%d%d",&x,&y);
link(x,y);link(y,x);
}
dfs(n,1);
printf("%d\n",dp[n][1]);
return 0;
}
CF1583F Defender of Childhood Dreams
我们先将 \(1,2,3,\dots ,k\) 这些点之间的所有边都染上颜色 \(1\) ,因为这 \(k\) 个点之间的最长边的长度也没到 \(k\) 。
同理,将 \(k+1,k+2,k+3,\dots ,2k\) 之间的所有边都染上颜色 \(1\) ,将 $2k+1,2k+2,2k+3,\dots ,3k$2 之间的所有边都染上颜色 \(1\) ,以此类推,最后到 \(k^2-k+1,k^2-k+2,k^2-k+3,\dots ,k^2\) 这些点之间的所有边都染上颜色 \(1\),简单来说,就是现将 \(n\) 以 \(k\) 个元素为一组分组,组内全部边都染 \(1\) 。
接下来,考虑将这 \(n\) 个数按照 \(k^2\) 个元素为一组再进行分组,属于同一组内的两个点间还没有染色的边都染上颜色 \(2\),仔细想想,这要就能保证这 \(k^2\) 个元素之间的所有长度等于 \(k\) 的路径上的颜色种类数至少有两个了。
以此类推,以 \(k^3\) 个元素划分组,组内还未染色的边全部染成 \(3\);再以 \(k^4\) 个元素划分组,组内还未染色的边全部染成 \(4……\)
所以最后最少的颜色数量是 \(\lceil \log_k n\rceil\) 。所有边的颜色上面都处理好了,直接输出即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,k;
int main(){
scanf("%d%d",&n,&k);
int ans=1,sum=k;
while(sum<n)ans++,sum*=k;
printf("%d\n",ans);
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
int x=i,y=j,d=0;
while(x!=y)x/=k,y/=k,d++;
printf("%d ",d);
}
}
return 0;
}