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\) 个点之一选择父亲,因此有转移式:

\[dp[i][j] =\prod_{fa_s=i}\sum_{k≤j+1}dp[s][k] \]

使用前缀和优化即可做到 \(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;
}


posted @ 2022-06-23 19:50  一粒夸克  阅读(145)  评论(0编辑  收藏  举报