Codeforces 记录

Codeforces 记录

CF2103E Keep the Sum

题面:

给一个长为 \(n\) 的序列 \(a\) 和一个数 \(K\),问是否可以执行若干次一下操作将其变为非单减数组。
每次操作为选择 \(i,j,i\not =j,a_i+a_j=K\),将两个位置变为任意 \([0,K]\) 之间的整数并仍然满足 \(a_i+a_j=K\)
输出方案(有解则存在 \(3n\) 操作数在之内的方案)或判断无解。\(n\leq 2\times 10^5,K\leq 10^9,0\leq a_i\leq K\)

题解:

当最开始无法进行操作并且此时序列不合法为 \(-1\),其余情况均能构造解。

考虑如何交换两个元素。设 \(a_i+a_j=K,i<j\),执行以下操作以交换 \(a_c,a_d\)

  • \(i,j\) 运算,使 \(a_i = K - a_c,a_j = a_c\)
  • \(i,c\) 运算,使 \(a_i=K-a_d,a_c=a_d\)
  • \(i,d\) 运算,使 \(a_i=K-a_j,a_d=a_j\)

此时已经做到对 \(i,j\) 之外的所有序列进行排序。

\(i,j\) 的处理方式是若 \(i=1,j=n\),则直接运算令 \(a_i=0,a_j=K\) 即可。
否则任选一个不是 \(1,n\) 的点(假设是 \(i\)):

  • \(i,j\) 运算,使 \(a_i=K-a_1,a_j=a_1\)
  • \(1,i\) 运算,使 \(a_1=K-a_n,a_i=a_n\)

此时 \(1,n\) 满足 \(a_1+a_n=K\),然后使用上述做法将 \([2,n]\) 排序并使 \(a_1=0,a_n=K\) 即可。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5;
int n,K,a[N],A,B,p[N],id[N];
map<int,int>mp;
vector<tuple<int,int,int> >ans;

void Swap(int x,int y){
	ans.emplace_back(A,B,a[x]-a[B]);
	a[A]=K-a[x],a[B]=a[x];
	ans.emplace_back(A,x,a[y]-a[x]);
	a[x]=a[y];a[A]=K-a[y];
	ans.emplace_back(A,y,a[B]-a[y]);
	a[y]=a[B];a[A]=K-a[B];
}

void solve(){
	n=read(),K=read();
	for(int i=1;i<=n;i++) a[i]=read();
	mp.clear();ans.clear();
	A=0;B=0;
	for(int i=1;i<=n;i++) 
		if(mp.find(K-a[i])!=mp.end()){
			B=i;A=mp[K-a[i]];
			break;
		}
		else mp[a[i]]=i;
	if(A==0){
		for(int i=1;i<=n;i++)
			if(a[i]<a[i-1]){
				puts("-1");
				return;
			}
		puts("0");
		return;
	}
	if(A!=1){
		ans.emplace_back(A,B,a[1]-a[B]);
		a[B]=a[1];a[A]=K-a[1];
		ans.emplace_back(1,A,a[n]-a[A]);
		a[1]=K-a[n];a[A]=a[n];
		A=1;B=n;
	}
	if(B!=n){
		ans.emplace_back(B,A,a[n]-a[A]);
		a[A]=a[n];a[B]=K-a[n];
		ans.emplace_back(n,B,a[1]-a[B]);
		a[B]=a[1];a[n]=K-a[1];
		A=1;B=n;
	}
	for(int i=2;i<n;i++) p[i]=i;
	sort(p+2,p+n,[&](int x,int y){ return a[x]<a[y]; });
	for(int i=2;i<n;i++) id[p[i]]=i;
	for(int i=2;i<n;i++){
		int j=p[i];
		Swap(i,j);
		int x=id[i];
		swap(p[i],p[x]);
		swap(id[j],id[i]);
	}
	ans.emplace_back(1,n,a[1]);
	printf("%lld\n",ans.size());
	int x,y,v;
	for(auto o:ans){
		tie(x,y,v)=o;
		printf("%d %d %d\n",x,y,v);
	}
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2097C Bermuda Triangle

题面:

在一个二位平面上有一个等腰直角三角形,三顶点是 \((0,0),(0,n),(n,0)\),有一个点初始在 \((x,y)\)\((v_x,v_y)\) 的向量移动,碰到边界按物理定律反弹。当与三角形某一顶点重合时结束,问撞击边界多少次(顶点不算),或永远无法出去。\(3\leq n\leq 10^9,1\leq x,y,x+y\leq n,1\leq v_x,v_y\leq 10^9\)

题解:

套路的我们不考虑反弹,而是将这个三角形无限扩大,让点以恒定不变的向量移动。
扩展后的平面如图:

即现在有同余方程组:

\[\begin{matrix} tv_x+x\equiv 0 \bmod n\\ tv_y+y\equiv 0 \bmod n \end{matrix} \]

你可以使用各种方式解的点的运动终点,设为 \((tx,ty)\)

现在问题变成了从 \((x,y)\) 走到 \((tx,ty)\) 经过边界多少次。
边界有四种,分别是:

  • \(y=kn,k\in \mathbb{Z}\),交点个数是 \(\lfloor\frac{ty}{n}\rfloor-1\)
  • \(x=kn,k\in \mathbb{Z}\),交点个数是 \(\lfloor\frac{tx}{n}\rfloor-1\)
  • \(x+y=(2k-1)n,k\in \mathbb{Z}\),交点个数是 \(\lfloor\frac{tx+ty}{2n}\rfloor\)
  • \(x-y=(2k-1)n,k\in \mathbb{Z}\),交点个数是 \(\lfloor\frac{|tx-ty|}{2n}\rfloor\)
代码
#include<bits/stdc++.h>
#define ll __int128
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

ll Abs(ll x){
	if(x<0) return -x;
	else return x;
}

ll gcd(ll a,ll b){
	return b?gcd(b,a%b):a;
}

void write(ll x){
	if(x>=10) write(x/10);
	putchar('0'+x%10);
}

void exgcd(ll a,ll b,ll &tx,ll &ty){
	if(b==0){
		tx=1;ty=0;
		return ;
	}
	ll x,y;
	exgcd(b,a%b,x,y);
	tx=y;ty=x-a/b*y;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--){
		ll n=read(),X=read(),Y=read(),dx=read(),dy=read();
		ll g=gcd(dx,dy);
		dx/=g;dy/=g;
		if(X%gcd(dx,n)||Y%gcd(dy,n)){
			puts("-1");
			continue;
		}
		ll gx=gcd(dx,n),gy=gcd(dy,n);
		ll Mx,My;
		{
			ll a,b;
			exgcd(dx/gx,n/gx,Mx,a);
			exgcd(dy/gy,n/gy,My,b);
			Mx*=-X/gx; My*=-Y/gy;
		}
		if(Abs(Mx-My)%gcd(n/gx,n/gy)){
			puts("-1");
			continue;
		}
		ll G=gcd(n/gx,n/gy);
		ll x,y;
		exgcd(n/gx/G,n/gy/G,x,y);
		x*=(My-Mx)/G;
		ll M=Mx+n/gx*x; M=(M%n+n)%n;
		ll tx=M*dx+X,ty=M*dy+Y;
		ll A=tx/n-1,B=ty/n-1,C=(tx+ty)/(2*n),D=Abs(tx-ty)/(2*n);
		write(A+B+C+D);
		puts("");
	}
	return 0;
}

CF2097D Homework

题面:

有两个长为 \(n\)\(01\)\(s,t\),问是否可以执行任意次操作后使 \(s=t\),判断 \(Yes/No\)
每次操作是选择两个数 \(a,b,v\),将区间 \([a\frac{n}{2^b}+1,(a+v)\frac{n}{2^b}]\)\([(a+v)\frac{n}{2^b}+1,(a+2v)\frac{n}{2^b}]\) 按位异或 \(a\in \mathbb{Z},2^b\mid n,v\in\{1,-1\}\),对于 \([l,r]\)\(l>r\) 则代表区间 \([r,l]\),两个区间必须都在 \([1,n]\) 内。 \(n\leq 10^6\)

题解:

\(n=mK,2\nmid m\)\(K\)\(2\) 的幂,根据题意,每 \(m\) 位要当作一个整体来看待,他不可以再拆分。
下面所说的序列的每一个元素都是一个 \(m\) 位串。
下面用 \(+\) 来代替异或。(因为 \(\oplus\) 难打)
性质一: 若对于 \((x,y)\) 可以操作成 \((x+y,y)\)\((x,y+x)\),那么 \((x,y)\) 可以变成 \((y,x)\)

\((x,y)\to (x,x+y)\to (y,x+y)\to (y,x)\)

性质二: 对于一个长度为 \(2^k,k>0\) 的序列 \(a\),可以做到对于任意 \(i\),将 \(i\) 异或到 \(i+2^{k-1}\)\(i\leq 2^{k-1}\))。

考虑归纳证明,假设所有 \(k'<k\) 均成立,边界是 \(k'=1\)。令 \(t=2^{k-1},x[1:t]=a[1:t],y[1:t]=a[t+1:2t]\)
\(i=1\),有如下构造:

\[\begin{pmatrix} x_1 \\ x_2 \\ \vdots \\ x_t \\ y_1 \\ y_2 \\ \vdots \\ y_t \end {pmatrix} \to \begin{pmatrix} x_1 + x_2 \\ x_1 \\ \vdots \\ x_t \\ y_1 \\ y_2 \\ \vdots \\ y_t \end{pmatrix} \to \begin{pmatrix} x_1 + x_2 \\ x_1 \\ \vdots \\ x_t \\ y_1 + x_1 + x_2 \\ y_2 + x_1 \\ \vdots \\ y_t + x_t \end{pmatrix} \to \begin{pmatrix} x_1 + x_2 \\ x_1 \\ \vdots \\ x_t \\ y_1 + y_2 + x_2 \\ y_2 + x_1 \\ \vdots \\ y_t + x_t \end{pmatrix} \to \begin{pmatrix} x_1 + x_2 \\ x_1 \\ \vdots \\ x_t \\ y_1 + y_2 + x_1 \\ y_2 \\ \vdots \\ y_t \end{pmatrix} \to \begin{pmatrix} x_1 \\ x_2 \\ \vdots \\ x_t \\ y_1 + x_1 \\ y_2 \\ \vdots \\ y_t \end{pmatrix} \]

\(i\not = 0\),将 \(i\)\(1\) 交换进行如上操作,再交换回来即可。根据性质一知道这是可行的。

性质三: 可以将序列的任意一个元素异或到任意一个位置。

性质二表明可以将距离为 \(2^k\) 的两个元素进行操作,那么对低于任意的数将距离二进制拆分并使用性质二,所以任意两元素均可操作。

于是我们可以直接对两个 \(m×k\) 的矩阵做高消,由于相似矩阵(能够通过线性变换变得相同的两个矩阵)高消之后结果相同,因此只要判这两个矩阵的高消之后的结果是否相同即可。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int B=62,N=2e6+5;
int X,Y,n,m,len;
char s[N];

void calc(char s[],vector<vector<ll> >&a){
	int id=1;
	for(int i=0;i<X;i++)
		for(int j=0;j<Y;j++){
			int v=s[id]-'0';
			if(v) a[i][j/B]|=1ll<<j%B;
			id++;
		}
}

int get(vector<vector<ll> >&a,int x,int y){
	return a[x][y/B]>>(y%B)&1;
}

void gauss(vector<vector<ll> >&a){
	int p=0;
	for(int i=0;i<Y;i++){
		int j=p;
		while(j<n&&!get(a,j,i)) j++;
		if(j>=n) continue;
		if(p!=j) swap(a[p],a[j]);
		for(int j=0;j<n;j++)
			if(j!=p&&get(a,j,i))
				for(int k=i/B;k<m;k++)
					a[j][k]^=a[p][k];
		p++;
	}
}

void solve(){
	len=read();
	X=len,Y=1;
	while(!(X&1)) X>>=1,Y<<=1;
	swap(X,Y);
	n=X;m=(Y-1)/B+1;
	vector<vector<ll> >a(n,vector<ll>(m));
	vector<vector<ll> >b(n,vector<ll>(m));
	scanf("%s",s+1);
	calc(s,a);gauss(a);
	scanf("%s",s+1);
	calc(s,b);gauss(b);
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			if(a[i][j]!=b[i][j]) return puts("No"),void();
	puts("Yes");
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2104 Numbers and Strings

题面:

对于一个整数 \(x\),定义 \(S(x)\) 为将 \(x,x+1\) 的十进制表示拼接再一起,并对得到的字符串不降序排序,计算 \(S(1),S(2),\cdots,S(n)\) 中不同字符串的个数。\(1\leq n\leq 10^9-2\)

题解:

考虑 \(x+1\)\(x\) 的变化,我们把 \(x\) 分成三部分:全为 \(9\) 的后缀,此后缀的前一位,除此之外的其它。那么 \(x+1\) 会将第二部分加一,将第一部分全部赋值为 \(0\),其余不变。所以如果两个数仅第三部分互为排列而一二部分相同则 \(S(x)\) 是相同的。根据题意,对于具有相同 \(S(x)\)\(x\),我们只保留最小的 \(x\)
直接按照上述形式搜索,要求第三部分单调不降(若有 \(0\) 则与第一个非 \(0\) 位置交换数字),然后对搜索出来的数字 \(S(x)\) 相同的再进行去重。询问可以直接再这些数组中二分。

这样搜索出来的数字数量是不多的:数字位数最多有 \(9\) 位,假设第三部分的长度是 \(k\),它满足单调不降,方案数是 \(\binom{k+9}{k}\);第二部分方案数是 \(9\);第三部分仅存在长度差异,方案数是 \((9-k)\)
这个上界是不严格的,总数肯定小于这个界,但这就足够了。\(\sum_{k=0}^8\binom{k+9}{k}9(9-k)\) 不超过 \(7\times 10^5\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

vector<pair<string,ll> >vec;
vector<ll>ans;

void calc(string s){
	int id=0;
	while(s[id]=='0') id++;
	swap(s[0],s[id]);
	ll x=0;
	for(char c:s) x=(x<<3)+(x<<1)+(c^48);
	s=to_string(x)+to_string(x+1);
	sort(s.begin(),s.end());
	vec.emplace_back(s,x);
}


void dfs(string s,bool fl,bool flg){
	if(flg) calc(s);
	if(s.size()<9){
		if(fl) dfs(s+"9",1,1);
		else{
			for(int i=0;i<=9;i++){
				char c='0'+i;
				bool nxt=c<s.back();
				s.push_back(c);
				dfs(s,nxt,bool(i)|flg);
				s.pop_back();
			}
		}
	}
}

void solve(){
	string s;
	for(int i=0;i<=9;i++){
		char c='0'+i;
		s.push_back(c);
		dfs(s,0,bool(i));	
		s.pop_back();
	}
	sort(vec.begin(),vec.end());
	for(int i=0;i<vec.size();i++)
		if(i==0||vec[i].first!=vec[i-1].first) ans.push_back(vec[i].second);
	sort(ans.begin(),ans.end());
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	solve();
	int T=read();
	while(T--){
		ll n=read();
		printf("%lld\n",upper_bound(ans.begin(),ans.end(),n)-ans.begin());
	}
	return 0;
}

CF2104G Modulo3

题面:

给定一个 \(n\) 个点的内向基环树森林,一次操作是选择一个点 \(x\) 和一种颜色 \(c\)\(1\leq x\leq n,1\leq c\leq k\)),将 \(x\) 和其能到达的所有点颜色染成 \(c\)\(Q\) 次询问,每次删除一条边再加入一条边,保证仍然是内向基环树森林,再给出 \(k\),问经过任意次上述操作后有多少种染色方案,初始每个点颜色为 \(1\),答案对 \(3\) 取模。\(n,q\leq 2\times 10^5,k\leq 10^9\)

题解:

对于一个基环树,环上所有点的颜色一定是一样的,其余点不管是什么颜色,都一定存在方案得到,构造考虑从叶子开始逐步染色。
每个基环树之间互不影响,令 \(len_T\) 表示基环树 \(T\) 的环长,所以答案就是 \(k^{n-\sum_{T}(len_T-1)}\)
由于求答案 \(\bmod 3\) 的值,根据扩展欧拉定理,只需求 \(n-\sum_T(len_T-1)\) 的奇偶性即可。
套路的,使用线段树分治来支持离线的加边删边操作,用可撤销并查集维护基环树,对于环长只需要边带权一个深度即可。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5;
int fa[N],siz[N],vis[N],dis[N],e[N],ans[N],K[N],n,Q,tim[N];
vector<pair<int&,int> >rev;
vector<pair<int,int> >t[N<<2];

void upd(int &x,int y){
	rev.push_back({x,x});
	x=y;
}

pair<int,int> find(int x){
	if(fa[x]==x) return {x,0};
	int u,v;
	tie(u,v)=find(fa[x]);
	return {u,v^dis[x]};
}

void merge(int x,int y,int &ans){
	int fx,fy,dx,dy;
	tie(fx,dx)=find(x);
	tie(fy,dy)=find(y);
	int d=dx^dy^1;
	if(fx==fy){
		if(vis[x]&&d){
			upd(vis[x],0);
			ans--;
		}
		return;
	}
	if(siz[fx]>siz[fy]) swap(fx,fy);
	ans-=vis[fx];ans-=vis[fy];
	upd(fa[fx],fy);
	upd(siz[fy],siz[fy]+siz[fx]);
	upd(dis[fx],d);
	upd(vis[fy],vis[fx]&vis[fy]);
	ans+=vis[fy];
}

void pop(int t){
	while(rev.size()>t){
		int &x=rev.back().first;
		int y=rev.back().second;
		x=y; rev.pop_back();
	}
}

void update(int s,int l,int r,int L,int R,pair<int,int> v){
	if(L<=l&&r<=R){
		t[s].push_back(v);
		return ;
	}
	int mid=l+r>>1;
	if(L<=mid) update(s<<1,l,mid,L,R,v);
	if(R>mid) update(s<<1|1,mid+1,r,L,R,v);
}

void dfs(int s,int l,int r,int ans){
	int tim=rev.size();
	int pre=ans;
	int x,y;
	for(auto o:t[s]){
		tie(x,y)=o;
		merge(x,y,ans);
	}
	if(l==r){
		if(K[l]%3==2) ::ans[l]=((n-ans)&1)?2:1;
		else ::ans[l]=K[l]%3;
	}
	else{
		int mid=l+r>>1;
		dfs(s<<1,l,mid,ans);
		dfs(s<<1|1,mid+1,r,ans);
	}
	ans=pre; pop(tim);
}


int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();Q=read();
	for(int i=1;i<=n;i++) e[i]=read();
	for(int i=1;i<=n;i++) tim[i]=1;
	for(int i=1;i<=Q;i++){
		int u=read(),v=read();K[i]=read();
		if(tim[u]<=i-1) update(1,1,Q,tim[u],i-1,{u,e[u]});
		e[u]=v; tim[u]=i;
	}
	for(int i=1;i<=n;i++) update(1,1,Q,tim[i],Q,{i,e[i]});
	for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1,dis[i]=0,vis[i]=1;
	dfs(1,1,Q,n);
	for(int i=1;i<=Q;i++) printf("%d ",ans[i]);
	return 0;
}

CF2062C Cirno and Operations

题面:

初始给定一个长为 \(n\) 的序列 \(a\),问执行若干次操作后 \(a\) 的和最大是多少。\(T\) 组数据。有两种操作:

  • \(a\) 数组反转为 \([a_n,a_{n-1},\cdots,a_1]\)
  • \(n>1\),用其差分数组代替它,即变为 \([a_2-a_1,a_3-a_2,\cdots,a_n-a_{n-1}]\)

\(T\leq 100,n\leq 50,|a_i|\leq 1000\)

题解:

对所有的操作方案按照操作二的次数分层。
每一层的所有方案的和的绝对值相等

考虑归纳,若此性质对于所有数组长度大于等于 \(n'\) 的均成立,归纳边界是 \(n'=n\)
连续进行两次一操作是没用的。
\(n'\) 转移到 \(n'-1\) 时:若直接进行操作二,总和变成 \(a_n'-a_1\);若进行操作一再进行操作二,总和变成 \(a_1-a_n'\)。绝对值不变。

除了最开始的状态外其余状态均可以取到正负两种和

除了最开始长为 \(n\) 的和只有一种情况之外,其余所有 \(n'\) 对下一层都会有上述正负两种转移方式,要么同时合法或不合法。

所以这题的解决方式就是暴力执行操作二,取得每一层值的绝对值,输出最大的那个。复杂度是 \(O(n^2)\) 的。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=55;
ll n,a[N];

ll Sum(){
	ll sum=0;
	for(int i=1;i<=n;i++) sum+=a[i];
	return llabs(sum);
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=a[i];
	while(n>1){
		for(int i=1;i<n;i++) a[i]=a[i+1]-a[i];
		n--;
		ans=max(ans,Sum());
	}
	printf("%lld\n",ans);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2062D Balanced Tree

题面:

给定一颗 \(n\) 个节点的树,你可以给 \(i\) 节点选择一个初始值 \(a_i\) 满足 \(l_i\leq a_i\leq r_i\)。你可以执行的一种操作是,选取两个点 \(u,v\),将以 \(u\) 为根的 \(v\) 子树中的 \(a_i\) 全部 \(+1\)。问当整棵树的 \(a_i\) 全部相同时最小的 \(a_i\) 是多少。(你不需要最小化操作次数)\(n\leq 2\times 10^5,l_i,r_i\leq 10^9\)

题解:

先考虑假设 \(l_i=r_i\),即每个点的初始值确定怎么做。对于相邻的两个点 \(x,fa\),若 \(a_x\not =a_{fa}\),对于改变他们两个值的差距有用的操作只有 \(u=x,v=fa\)\(u=fa,v=x\) 这两种,运算后差值减小 \(1\),所以总是需要运算 \(|a_x-a_{fa}|\) 次,对于每条边都要这样做。考虑全部操作完后 \(1\) 节点的值即答案就是 \(a_1+\sum\max(a_x-a_{fa(x)},0)\)

接下来考虑如何选择初始值,假设 \(x\) 的所有儿子节点初始值都选好了。
\(r_x=+\infty\),那么 \(a_x\geq \max a_v\) 证明考虑如果 \(a_x<\max a_v\) 那么将 \(a_x\) 增加一,\(a_x-a_{fa(x)}\) 会至多增加一,而 \(\sum a_v-a_x\) 会至少减少一,一定不劣。
同时显然的取值越小越好,所以有 \(a_x=\min(r_x,\max(l_x,\max a_v))\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5;
int l[N],r[N],n,fat[N],a[N];
vector<int>e[N];

void dfs(int x,int fa){
	int mx=l[x];
	for(int v:e[x])
		if(v!=fa){
			fat[v]=x;
			dfs(v,x);
			mx=max(mx,a[v]);
		}
	mx=min(mx,r[x]);
	a[x]=mx;
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++) l[i]=read(),r[i]=read();
	for(int i=1;i<=n;i++) e[i].clear();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	ll ans=0;
	for(int i=1;i<=n;i++) ans+=max(0,a[i]-a[fat[i]]);
	printf("%lld\n",ans);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2062E The Game

题面:

给定一颗 \(n\) 个点的带点权有根树,两个人轮流操作,每次操作是选择一个尚未被删除的且点权严格大于之前所选点的点,并删除其子树,谁先无法操作谁获胜。
E1:输出任意一个先手必胜策略的第一步点。
E2:输出所有先手必胜策略的第一步点。
\(n\leq 4\times 10^5\)

题解:

首先这个游戏先手必胜一定是双方各选一个点然后结束游戏。 否则假设两人依次选点 \(a,b,c,d\) 然后结束,那么第二个人可以直接选则 \(c\) 变成选点 \(a,c,d\) 而获胜。

首先合法点的一个必要条件肯定是其子树外存在一个点的点权比它大。

考虑 E1,由于我们只需要求出一个点即可,所以可以直接选择满足上述必要条件中点权最大的那个点,容易验证这样是对的。
实现是简单的,只需要使用 \(dfs\) 序来判断点是否在子树内,维护前后缀 \(\max\) 合并即可。

考虑 E2,我们需要再增加跟准确的限制条件来便于我们对所有点进行判断。
假设两人依次选择点 \(u,v\),考虑对于一个已知的 \(v\) 什么样的 \(u\) 是合法的。设 \(g(v)\) 表示所有不在 \(v\) 子树内且点权大于 \(v\) 的点的 \(LCA\)。那么根据上述结论,\(u\) 必须是 \(g(v)\) 或其祖先。
那现在的条件变成了对于第一个点 \(u\),需要使得所有点权大于 \(u\) 的点 \(v\)\(g(v)\) 都在 \(u\) 的子树中,或者 \(v\) 本身在 \(u\) 的子树中。这是充要的。
如何求 \(g(u)\)

从大到小枚举点,由于 \(LCA\) 具有结合律,所以可以 \(dfs\) 序加线段树等方式容易维护动态加点求子树外 \(LCA\)

如何求答案?

从大到小枚举点,对每个点 \(x\)\(g(x)\) 的所有祖先的并集进行链加,只有这个集合中的点是符合 \(x\) 的限制的。枚举到当前点时所有点权大于它的都已经扫过了,看是否符合全部限制即可。

根据实现方式不同可以做到一只或两只 \(\log\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=4e5+5;
int siz[N],son[N],fat[N],dep[N],top[N],dfn[N],id[N],tim,n,a[N],low[N],f[N],lca[N];
vector<int>e[N],vec[N];

void dfs1(int x,int fa){
	siz[x]=1; son[x]=0;
	for(int v:e[x])	
		if(v!=fa){
			fat[v]=x;
			dep[v]=dep[x]+1;
			dfs1(v,x);
			siz[x]+=siz[v];
			if(siz[v]>siz[son[x]]) son[x]=v;
		}
}

void dfs2(int x,int t){
	top[x]=t;dfn[x]=++tim;id[tim]=x;
	if(!son[x]) return ;
	dfs2(son[x],t);
	for(int v:e[x])
		if(v!=fat[x]&&v!=son[x]) dfs2(v,v);
}

namespace LCAF{
	int lgn[N<<1],st[22][N<<1],dfn[N],tim;
	
	int cmin(int x,int y){
		return dep[x]<dep[y]?x:y;
	}
	
	int LCA(int x,int y){
		if(!x) return y;
		if(!y) return x;
		int l=dfn[x],r=dfn[y];
		if(l>r) swap(l,r);
		int mn=lgn[r-l+1];
		return cmin(st[mn][l],st[mn][r-(1<<mn)+1]);
	}
	
	void dfs(int x,int fa){
		st[0][++tim]=x;dfn[x]=tim;
		for(int v:e[x])
			if(v!=fa){
				dfs(v,x);
				st[0][++tim]=x;
			}
	}
	
	void init(){
		tim=0; dfs(1,0);
		for(int i=2;i<=n<<1;i++) lgn[i]=lgn[i>>1]+1;
		for(int j=1;j<=lgn[n<<1];j++)
			for(int i=1;i+(1<<j)-1<=n<<1;i++)
				st[j][i]=cmin(st[j-1][i],st[j-1][i+(1<<j-1)]);
	}
}
using LCAF::LCA;
using LCAF::cmin;

namespace sub1{
	int t[N<<2];
	
	void pushup(int s){
		t[s]=LCA(t[s<<1],t[s<<1|1]);
	}
	
	void build(int s,int l,int r){
		t[s]=0;
		if(l==r) return ;
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
	}
	
	void update(int s,int l,int r,int x,int v){
		if(l==r){
			t[s]=v;
			return ;
		}
		int mid=l+r>>1;
		if(x<=mid) update(s<<1,l,mid,x,v);
		else update(s<<1|1,mid+1,r,x,v);
		pushup(s);
	}
	
	int query(int s,int l,int r,int L,int R){
		if(L<=l&&r<=R) return t[s];
		int mid=l+r>>1;
		if(R<=mid) return query(s<<1,l,mid,L,R);
		if(L>mid) return query(s<<1|1,mid+1,r,L,R);
		return LCA(query(s<<1,l,mid,L,R),query(s<<1|1,mid+1,r,L,R));
	}
}

namespace sub2{
	int c[N];
	
	void clear(int n){
		for(int i=1;i<=n;i++) c[i]=0;
	}
	
	void add(int x,int v){
		while(x<=n){
			c[x]+=v;
			x+=x&-x;
		}
	}
	
	int query(int x){
		int res=0;
		while(x>0){
			res+=c[x];
			x-=x&-x;
		}
		return res;
	}
	
	void update(int x,int v,int y=1){
		while(top[x]!=top[y]){
			if(dep[top[x]]<dep[top[y]]) swap(x,y);
			add(dfn[top[x]],v);
			add(dfn[x]+1,-v);
			x=fat[top[x]];
		}
		if(dep[x]>dep[y]) swap(x,y);
		add(dfn[x],v);
		add(dfn[y]+1,-v);
	}
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++) e[i].clear();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(1,0); tim=0; dfs2(1,1);
	LCAF::init();
	for(int i=1;i<=n;i++) low[i]=dfn[i]+siz[i]-1;
	for(int i=1;i<=n;i++) vec[i].clear();
	for(int i=1;i<=n;i++) vec[a[i]].push_back(i);
	sub1::build(1,1,n);
	for(int i=n;i>=1;i--){
		for(int x:vec[i]){
			int l=dfn[x]-1,r=low[x]+1,lca=0;
			if(1<=l&&r<=n) lca=LCA(sub1::query(1,1,n,1,l),sub1::query(1,1,n,r,n));
			else if(1<=l) lca=sub1::query(1,1,n,1,l);
			else if(r<=n) lca=sub1::query(1,1,n,r,n);
			f[x]=lca;
		}
		for(int x:vec[i]) sub1::update(1,1,n,dfn[x],x);
	}
	lca[n+1]=0;
	for(int i=n;i>=1;i--){
		lca[i]=lca[i+1];
		for(int x:vec[i]) lca[i]=LCA(lca[i],x);
	}
	sub2::clear(n);
	int cnt=0;
	vector<int>ans;
	for(int i=n;i>=1;i--){
		for(int x:vec[i])
			if(!(dfn[x]<=dfn[lca[i+1]]&&dfn[lca[i+1]]<=low[x])&&lca[i+1]!=0&&sub2::query(dfn[x])==cnt) ans.push_back(x);
		for(int x:vec[i]){
			if(f[x]){
				sub2::update(x,1);
				sub2::update(f[x],1);
				sub2::update(LCA(x,f[x]),-1);
				cnt++;
			}
		}
	}
	printf("%d ",ans.size());
	sort(ans.begin(),ans.end());
	for(int x:ans) printf("%d ",x);puts("");
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2062F Traveling Salescat

题面:

有一张 \(n\) 个点的完全图,每个点有参数 \(a_i,b_i\),边 \((u,v)\) 的边权是 \(\max(a_i+b_j,a_j+b_i)\)。对于 \(k=2,3,\cdots,n\) 求经过 \(k\) 个不同点简单路径的最小权值。\(n\leq 3000\)

题解:

边权是 \(\max\) 的这个形式非常的不好,它导致我们即使知道点的集合也无法确定最小的行走方式,考虑先解决这个问题。
\(\max\) 变形有:

\[\max(x,y)=\frac{(x+y)+|x-y|}{2} \]

\(c_i=a_i+b_i,d_i=a_i-b_i\)\(w(u,v)=\frac{1}{2}(c_i+c_j+|d_i-d_j|)\)
对于 \(c\) 的贡献就是端点贡献一次,中间所有点贡献两次。
对于 \(d\) 的贡献就是假设路径起终点值是 \(d_s,d_t\),将其余点按 \(d\) 排序依次走,那么贡献是 \(2(\max d_i-\min d_i)-|d_s-d_t|\)

关键点有四种,即 \(d\) 的极值点和起终点。于是按 \(d\) 排序后,设 \(f_{i,j,c}\) 表示考虑 \(1\sim i\),选择 \(j\) 个点,其中选择了前 \(c\) 个关键点(顺序为 \(\min\to s\to t\to \max\))的最小贡献。直接 \(dp\) 即可。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=3e3+5;
const ll inf=1e18;
int p[N];
ll x[N],y[N];
ll f[N][N][4],ans[N];

void solve(){
	int n=read();
	for(int i=1;i<=n;i++){
		ll a=read(),b=read();
		x[i]=a+b,y[i]=a-b;
	}
	for(int i=1;i<=n;i++) p[i]=i;
	sort(p+1,p+1+n,[&](int a,int b){ return y[a]<y[b]; });
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++)
			for(int k=0;k<=3;k++) f[i][j][k]=inf;
	for(int i=1;i<=n;i++) ans[i]=inf;
	for(int i=1;i<=n;i++){
		int v=p[i];
		f[i][1][1]=min(f[i-1][1][1],2*(x[v]-y[v]));
		f[i][1][2]=min(f[i-1][1][2],x[v]-y[v]);
		for(int j=1;j<=i;j++){
			for(int k=1;k<=3;k++){
				f[i][j][k]=min(f[i][j][k],f[i-1][j][k]);
				f[i][j][k]=min(f[i][j][k],f[i-1][j-1][k]+2*x[v]);
			}
			f[i][j][2]=min(f[i][j][2],f[i-1][j-1][1]+x[v]+y[v]);
			f[i][j][3]=min(f[i][j][3],f[i-1][j-1][2]+x[v]-y[v]);
			ans[j]=min(ans[j],f[i][j][3]+2*y[v]);
		}
	}
	for(int i=2;i<=n;i++) printf("%lld ",ans[i]>>1);
	puts("");
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	memset(f,0x3f,sizeof(f));
	int T=read();
	while(T--) solve();
	return 0;
}

CF2062G Permutation Factory

题面:

给你长为 \(n\) 的初始排列 \(p\) 和终止排列 \(q\),每次可以交换两个位置 \(i,j\),代价是 \(\min(|i-j|,|p_i-p_j|)\),问将 \(p\) 变为 \(q\) 的最小代价,需要输出方案。\(n\leq 100\)

题解:

因为代价是两个取 \(\min\),而求的是最小值,所以可以看作是每次用 \(|i-j|\)\(|p_i-p_j|\) 的代价交换两个数,答案不变。巧妙地发现两个代价计算用到的值相对独立,可以看做是二维平面上有 \(n\) 个点 \((i,p_i)\),发现一次操作的代价就是点移动距离的一半。进一步的,我们不妨假设可以每次只移动一个点,代价是曼哈顿距离。
所以现在的题目有下界是,给每个 \((i,p_i)\) 匹配一个 \((j,q_j)\) 代价是 \(|i-j|+|p_i-q_j|\) 的最小代价。这个下界是容易求的,直接二分图最小权匹配即可。

同时这个下界是能取到的,构造方式如下:
首先横纵坐标互不影响,可以同理构造,我们来只考虑横坐标。设原来第 \(i\) 个点的当前坐标是 \((x_i​,y_i​)\),它匹配的目标点是 \((x_i',y_i')\)。每次找到一个 \(x_i\not = x_i'\) ​且 \(x_i'\) ​最小的 \(i\),此时一定有 \(x_i'<x_i\)​。
然后一定能找一个 \(j\) 使得 \(x_i'​\leq x_j​<x_i​\leq x_j'\)​(因为满足 \(x_i'\leq x_j < x_i\)\(j\)\(x_i-x_i'\) 个,但是不满足 \(x_i\leq x_j'\)\(j\) 只有 \(x_i-x_i'-1\) 个),将 \((x_i​,y_i​)\) 移动到 \((x_j​,y_i​)\)\((x_j​,y_j​)\) 移动到 \((x_i​,y_j​)\)。体现在原排列上就是交换 \((p_{x_i}​​,p_{x_j}​​)\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=105,M=N<<1,inf=1e9+7;
int cnt=1,head[M],dis[M],S,T,h[M],pre[M],lst[M],n,p[N],q[N],Cnt[N][N];
pair<int,int>A[N],B[N];
bool vis[M];
struct edge{
	int v,nxt,c,w;
}e[N*N+N+N<<1];

void add(int u,int v,int c,int w){
	e[++cnt]={v,head[u],c,w};
	head[u]=cnt;
	e[++cnt]={u,head[v],0,-w};
	head[v]=cnt;
}

bool dijkstra(){
	for(int i=S;i<=T;i++) vis[i]=0,dis[i]=inf;
	priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
	q.push({0,S}); dis[S]=0;
	while(!q.empty()){
		int x=q.top().second;
		q.pop();
		if(vis[x]) continue;
		vis[x]=1;
		for(int i=head[x],v,w;i;i=e[i].nxt){
			v=e[i].v;w=h[x]-h[v]+e[i].w;
			if(e[i].c&&dis[v]>dis[x]+w){
				dis[v]=dis[x]+w;
				pre[v]=x;lst[v]=i;
				q.push({dis[v],v});
			}
		}
	}
	return dis[T]<=inf/2;
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++) p[i]=read();
	for(int i=1;i<=n;i++) q[i]=read();
	S=0;T=n<<1|1;
	for(int i=1;i<=n;i++) add(S,i,1,0);
	for(int i=1;i<=n;i++) add(i+n,T,1,0);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			add(i,j+n,1,abs(i-j)+abs(p[i]-q[j]));
			Cnt[i][j]=cnt-1;
		}
	while(dijkstra()){
		for(int i=S;i<=T;i++) h[i]+=dis[i];
		int w=inf;
		for(int x=T;x!=S;x=pre[x]) w=min(w,e[lst[x]].c);
		for(int x=T;x!=S;x=pre[x]){
			e[lst[x]].c-=w;
			e[lst[x]^1].c+=w;
		}
	}
	for(int i=1;i<=n;i++){
		A[i]={i,p[i]};
		for(int j=1;j<=n;j++)
			if(!e[Cnt[i][j]].c){
				B[i]={j,q[j]};
				break;
			}
	}
	vector<pair<int,int> >ans;
	while(1){
		int mn=1e9,x=-1;
		for(int i=1;i<=n;i++)
			if(A[i].first!=B[i].first&&B[i].first<mn){
				mn=B[i].first;
				x=i;
			}
		if(!~x) break;
		for(int i=1;i<=n;i++)
			if(B[x].first<=A[i].first&&A[i].first<A[x].first&&A[x].first<=B[i].first){
				ans.push_back({A[x].first,A[i].first});
				swap(A[x].first,A[i].first);
				break;
			}
	}
	while(1){
		int mn=1e9,x=-1;
		for(int i=1;i<=n;i++)
			if(A[i].second!=B[i].second&&B[i].second<mn){
				mn=B[i].second;
				x=i;
			}
		if(!~x) break;
		for(int i=1;i<=n;i++)
			if(B[x].second<=A[i].second&&A[i].second<A[x].second&&A[x].second<=B[i].second){
				ans.push_back({A[x].first,A[i].first});
				swap(A[x].second,A[i].second);
				break;
			}
	}
	printf("%lld\n",ans.size());
	for(pair<int,int> x:ans) printf("%d %d\n",x.first,x.second);
	cnt=1;
	for(int i=S;i<=T;i++) head[i]=0,h[i]=0;
}

int main(){
//	freopen("data.in","r",stdin);
//	freopen("me.out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2062H Galaxy Generator

题面:

给定一个 \(n\times n\) 的矩阵 \(a\)\(a_{x,y}=1\) 表示二维平面上 \((x,y)\) 处有一个点,定义两个点相连当且仅当两个点的 \(x\)\(y\) 坐标相同,且它们之间的线段上没有其他点时。你可以在某个坐标处创建一个新点当且仅当创建完后这个新点会与至少三个点相连,问最少能形成多少个联通块。对于所有非空子集的答案求和。\(n\leq 14\)

题解:

对于一个确定的集合 \(S\),显然他的最小连通块取到的情况就是把所有能创建的点全部创建出来,直到不能在创建,创建顺序并不重要,最终的状态都是一样的。

考虑什么两个连通块能合并成一个的充要条件:找一个最小的矩形包含连通块内的所有点,如果两个矩形在某个坐标轴上投影出来的区间有交,则两个连通块可以合并成一个。由此我们可以快速的得出一个情况的答案就是将所有矩形在坐标轴上投影,能合并就合并,最终得到若干投影互不相交的矩形,此时矩形个数就是答案。

那么可以直接对不交的矩形集合进行 \(dp\),设 \(sub(l,r)\) 表示集合 \(\{l,\cdots,r\}\)\(f_{i,S}\) 表示考虑前 \(i\) 行,\(S\) 集合中的列没有被已选择的矩形投影覆盖的方案数。转移则考虑 \(i\) 行是否有点被选择,若没有就是 \(f_{i,S}\to f_{i+1,S}\);否则枚举这个矩形是 \((i,j;l,r)\),转移是 \(f_{i,S}\to f_{j+1,S-sub(l,r)}\),系数是在 \((i,j;l,r)\) 中选若干点是的合并后的矩形恰好是 \((i,j;l,r)\) 的方案数(记作 \(w_{i,l,j,r}\))。

假设已经求出 \(w_{i,l,j,r}\),设 \(g_{i,S}\),状态含义与 \(f\) 相同,表示答案。最终答案为 \(\sum_S g_{n,S}\)。有类似的转移 \(g_{i,S}\to g_{i+1,S}\)\(g_{i,S}\times w_{i,l,j,r}+1\times f_{i,S}\times w_{i,l,j,r}\to g_{j+1,S-sub(l,r)}\)

\(w\) 的方法是,先忽略必须形成 \((i,j;l,r)\),而是考虑在 \((i,j;l,r)\) 中选择若干点能形成一个矩形的方案数 \(C_{i,l,j,r}\)。那么对 \((i,j;l,r)\) 的四条边是否包含在矩形内进行容斥,由 \(C_{i(+1),l(+1),j(-1),r(-1)}\) 可以的到 \(w\) 的值。

\(C\) 同样使用容斥来算,首先不作任何限制,方案是 \(2^{sum(i,l,j,r)}-1\)\(sum(i,l,j,r)\) 表示 \((i,j;l,r)\) 中的点的个数,然后减去至少有两个矩形的方案数。枚举至少有两个矩形的情况中最靠上的矩形的位置 \((sx,ex;sy,ey)\),设 \(h_{l,r,S}\) 表示在行 \(l,r\) 中选择若干矩形(可以一个都不选),覆盖的列集合是 \(S\) 的子集的方案数,容斥方程就是 \((-1)\times w_{sx,sy,ex,ey}\times (h_{ex+1,j,sub(l,r)-sub(sy,ey)}-1)\to C_{i,l,j,r}\)

关于 \(h\) 的转移同理考虑第一个矩形的位置为 \((l,k;L,R)\),有转移 \(w_{l,L,k,R}\times h_{k+1,r,s-sub(L,R)}\to h_{l,r,S}\)

复杂度是 \(O(\binom{n+4}{4}^2+2^nn^4)\)

代码中的变量名和题解变量名一致。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=16,M=(1<<14)+5,mod=1e9+7;
int n,fac[N*N],a[N][N],poc[20];
char s[N][N];
ll f[N][M],g[N][M],h[N][N][M];
ll C[N][N][N][N],W[N][N][N][N];

ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}

int calc(int sx,int sy,int ex,int ey){
	if(sx>ex||sy>ey) return 0;
	int ans=a[ex][ey];
	if(sx-1>=0) ans-=a[sx-1][ey];
	if(sy-1>=0) ans-=a[ex][sy-1];
	if(sx-1>=0&&sy-1>=0) ans+=a[sx-1][sy-1];
	return ans;
}

void solve(){
	n=read();
	for(int i=0;i<n;i++) scanf("%s",s[i]);
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++) a[i][j]=s[i][j]-'0';
	for(int i=0;i<n;i++)
		for(int j=1;j<n;j++) a[i][j]+=a[i][j-1];
	for(int i=1;i<n;i++)
		for(int j=0;j<n;j++) a[i][j]+=a[i-1][j];
	for(int lx=n-1;lx>=0;lx--){
		int l=lx+1;
		for(int s=0;s<1<<n;s++) h[l][l-1][s]=1;
		for(int r=l;r<n;r++)
			for(int s=0;s<1<<n;s++) h[l][r][s]=h[l+1][r][s];
		for(int r=l;r<n;r++)
			for(int s=0;s<1<<n;s++)
				for(int L=0;L<n;L++) if(s>>L&1)
					for(int R=L,t=s;R<n&&(t>>R&1);R++){
						t^=1<<R;
						for(int k=l;k<=r;k++)
							(h[l][r][s]+=W[l][L][k][R]*h[k+1][r][t])%=mod;
					}
		for(int rx=lx;rx<n;rx++)
			for(int ly=n-1;ly>=0;ly--)
				for(int ry=ly,s=0;ry<n;ry++){
					s|=1<<ry;
					C[lx][ly][rx][ry]=Mod(fac[calc(lx,ly,rx,ry)]+mod-1);
					for(int sx=lx;sx<rx;sx++)
						for(int ex=sx;ex<=rx;ex++)
							for(int sy=ly;sy<=ry;sy++)
								for(int ey=sy,t=s;ey<=ry;ey++){
									t^=1<<ey;
									Add(C[lx][ly][rx][ry],mod-W[sx][sy][ex][ey]*Mod(h[ex+1][rx][t]+mod-1)%mod);
								}
					W[lx][ly][rx][ry]=0;
					for(int s=0;s<1<<4;s++){
						int sx=lx+(s>>0&1);
						int sy=ly+(s>>1&1);
						int ex=rx-(s>>2&1);
						int ey=ry-(s>>3&1);
						if(sx<=ex&&sy<=ey){
							if(!(poc[s]&1)) Add(W[lx][ly][rx][ry],C[sx][sy][ex][ey]); 
							else Add(W[lx][ly][rx][ry],mod-C[sx][sy][ex][ey]);
						}
					}
				}
	}
	for(int i=0;i<=n;i++)
		for(int s=0;s<1<<n;s++) f[i][s]=g[i][s]=0;
	f[0][(1<<n)-1]=1;
	for(int i=0;i<n;i++)
		for(int s=0;s<1<<n;s++){
			Add(f[i+1][s],f[i][s]);
			Add(g[i+1][s],g[i][s]);
			for(int l=0;l<n;l++) if(s>>l&1)
				for(int r=l,t=s;r<n&&(t>>r&1);r++){
					t^=1<<r;
					for(int j=i;j<n;j++){
						(f[j+1][t]+=f[i][s]*W[i][l][j][r])%=mod;
						(g[j+1][t]+=Mod(g[i][s]+f[i][s])*W[i][l][j][r])%=mod;
					}
				}
		}
	ll ans=0;
	for(int s=0;s<1<<n;s++) Add(ans,g[n][s]);
	printf("%lld\n",ans);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	fac[0]=1;
	for(int i=1;i<N*N;i++) fac[i]=Mod(fac[i-1]*2);
	for(int s=0;s<1<<4;s++) poc[s]=__builtin_popcount(s);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2118E Grid Coloring

题面:

给出 \(n,m\),你需要在一个 \(n,m\) 的网格上逐个染色,每次染色一个,一个格子染一次,每次染色后距离这个格子最远的并且已经染过色的格子惩罚代价 \(+1\),如果有多个最远的格子则所有最远的代价都增加。构造一种方案使得染完所有格子并且没有格子的代价超过 \(3\)

格子 \((sx,sy)\) 比格子 \((ex,ey)\) 距离 \((x,y)\) 更远即满足 \(\max(|x-sx|,|y-sy|)>\max(|x-ex|,|y-ey|)\),或上式相等且 \(|x-sx|+|y-sy|>|x-ex|+|y-ey|\)

\(1\leq n,m,nm\leq 5000\)\(n,m\) 都是奇数,可以证明解总是存在的。

题解:

显然染色顺序大致上应该从中间逐渐向四周扩展,\(n=1\) 的染色方案也说明了这个问题。

考虑现在你有 \(A\times B\) 的矩阵已经染好了,可以先取两组对边 \((i;l,r)\)\((j;l,r)\),染色方式是 \((i,mid),(j,mid),(i,mid-1),(j,mid+1),(i,mid+1),(j,mid-1),\cdots\),扩展到 \((A+2)\times B\) 的矩阵,再以同样的方式扩展到 \((A+2)\times (B+2)\),直到 \(\min(n,m)\times \min(n,m)\)。然后就只用扩展一边了,变成 \((\min(n,m)+2)\times \min(n,m)\),直到 \(\max(n,m)\times \min(n,m)\)

example:

60 46 42 38 36 40 44 49 63 
56 32 22 18 16 20 25 35 59 
52 28 12 06 04 09 15 31 55 
50 26 10 02 01 03 11 27 51 
54 30 14 08 05 07 13 29 53 
58 34 24 21 17 19 23 33 57 
62 48 45 41 37 39 43 47 61

进一步的,直接把所有点按照距离中心点的切比雪夫距离为第一关键字,曼哈顿距离为第二关键字排序后依次填入,和上述方案是一样的,代码极度好写。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

int n,m;

void solve(){
	n=read();m=read();
	int X=n+1>>1,Y=m+1>>1;
	vector<pair<int,int> >ans;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) ans.push_back({i,j});
	stable_sort(ans.begin(),ans.end(),[&](pair<int,int> x,pair<int,int> y){
		int vxa=abs(X-x.first),vxb=abs(Y-x.second);
		int vya=abs(X-y.first),vyb=abs(Y-y.second);
		int vx=max(vxa,vxb),vy=max(vya,vyb);
		if(vx!=vy) return vx<vy;
		else return vxa+vxb<vya+vyb;
	});
	for(auto x:ans) printf("%d %d\n",x.first,x.second);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2174C2 Beautiful Patterns (Hard Version)

题面:

一个字符串的价值定义为其非空回文子串个数的平方。

求长为 \(n\) 的字符集大小是 \(m\) 的所有子串的价值期望模质数 \(p\)\(1\leq n\leq 2\times 10^5,1\leq m\leq 10^7\)

题解:

套路一步,将个数的平方期望转化为任选两个区间其都是回文的概率之和。

考虑一个长 \(s\) 的区间是回文的概率,前一半可以随便选,后一半都是确定的,即 \(\frac{m^{(s+1)/2}}{m^s}\),记为 \(a_s\)

如果两个区间的中心相同,则显然都是回文的概率是长区间的概率。

如果不同则概率是 \(a_i\times a_j\)\(i,j\) 是两区间长度。

证明这一点考虑两区间如果不交是显然的

如果相交那么这些位置一定可以分成若干组,每组的位置之间都可以通过若干次回文对称操作到达,而没有一个位置同时处于两组中。因此,设相交长度为 \(len\),如果在第一个串中任意地分配了 \((i+1)/2\) 个,那么在第二个串中只能选择 \((j+1)/2−len\) ,所以概率等于 \(\frac{m^{(i+1)/2+(j+1)/2-len}}{m^{i+j-len}}=a_i\times a_j\)

考虑若两区间长度是 \(i,j(i\geq j)\) ,中心相同的有 \([j-i\equiv 0\pmod{2}](n-i+1)\) 对贡献是 \(a_i\),否则是 \(a_ia_j\)

所以答案等于

\[2\sum_i(n-i+1)a_i\big(\sum_{j<i}(n-j+1-[j-i\equiv 0])a_j+\sum_{j<i}[j-i\equiv 0]\big)+\sum_i (n-i+1)(n-i)a_i^2+\sum_i(n-i+1)a_i \]

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
    ll s=0,k=1;
    char c=getchar();
    while(c>'9'||c<'0'){
        if(c=='-') k=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        s=(s<<3)+(s<<1)+(c^48);
        c=getchar();
    }
    return s*k;
}

const int N=2e5+5;
ll n,m,mod;
ll a[N],sum[N][2];

ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}

ll ksm(ll a,ll b){
    ll t=1;
    for(;b>0;b>>=1,a=a*a%mod)
        if(b&1) t=t*a%mod;
    return t;
}

void solve(){
    n=read();m=read();mod=read();
    for(int i=1;i<=n;i++) 
        a[i]=ksm(m,i+1>>1)*ksm(ksm(m,i),mod-2)%mod;
    for(int i=1;i<=n;i++){
        sum[i][0]=sum[i-1][0];
        sum[i][1]=sum[i-1][1];
        Add(sum[i][i&1],a[i]);
    }
    ll S=0,ans=0;
    for(int i=1;i<=n;i++){
        ll val=S;
        Add(val,mod-sum[i-1][i&1]);
        Add(val,(i+1>>1)-1);
        (ans+=(n-i+1)*a[i]%mod*val%mod*2)%=mod;
        (S+=a[i]*(n-i+1))%=mod;
        (ans+=(n-i+1)*a[i])%=mod;
        (ans+=a[i]*a[i]%mod*(n-i+1)%mod*(n-i)%mod)%=mod;
    }
    printf("%lld\n",ans);
}

int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    int T=read();
    while(T--) solve();
    return 0;
}

CF2174F Mosaic Tree

题面:

\(n\) 个点 \(m\) 种颜色,每个点颜色 \(a_i\),问有多少个带标号无根树满足颜色为 \(i\) 的点的度数之和奇偶性是 \(s_i\)\(1\leq n,m\leq 10^4\)

题解:

特判 \(n=1\)

记颜色为 \(i\) 的点有 \(c_i\) 个。

考虑 prufer 序列,每个点的度数是其在 prufer 序列中出现次数加一,所以令 \(s_i=(s_i-c_i)\bmod 2\)

问题变为多少个长为 \(n-2\) 的值域是 \(1\sim n\) 的序列,颜色是 \(i\) 的点出现次数之和奇偶性是 \(s_i\)

\(f_{i,j}\) 表示前 \(i\) 种颜色选了 \(j\) 个点,转移是 \(f_{i-1,j-k}\binom j k c_i^k\to f_{i,j}\) 要求 \(k\bmod 2=s_i\)。移项得 \(\frac {f_{i-1,j-k}}{(j-k)!}\times \frac {c_i^k}{k!} \to \frac {f_{i,j}}{i!}\)

从 EGF 角度考虑生成函数 \(e^{kz}=\sum_j\frac{(kz)^j}{j!}\)

设颜色 \(k\) 的生成函数为 \(G_k(z)\)

如果 \(s_i=0\) 则只取偶数项 \(G_k(z)=(e^{c_kz}+e^{-c_kz})/2\)

如果 \(s_i=1\) 则只取奇数项 \(G_k(z)=(e^{c_kz}-e^{-c_kz})/2\)

答案是 \((n-2)!\times [z^{n-2}]\prod_{i=1}^m G_i(z)\)

考虑后面这个东西是 \(\frac{1}{2^m}\prod_{i=1}^m(e^{c_kz}\pm e^{-c_kz})\),考虑它展开后的每一项是 \(c\times e^{vz}\)。所以设 \(f_v\) 表示考虑前 \(i\) 个生成函数展开后的每一项,\(e^{vz}\) 前的系数 \(c\) 之和。

如果选 \(e^{c_kz}\),那么转移是 \(f_v\to f_{v+c_k}\)

如果选 \(e^{-c_kz}\),根据 \(s_i\) 确定转移是 \(\pm f_v\to f_{v-c_k}\)

\((n-2)!\) 与指数生成函数的分母抵消,最终答案是 \(\frac{1}{2^m}\sum_v f_v\times v^{n-2}\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
    ll s=0,k=1;
    char c=getchar();
    while(c>'9'||c<'0'){
        if(c=='-') k=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        s=(s<<3)+(s<<1)+(c^48);
        c=getchar();
    }
    return s*k;
}

const int N=1e4+5,mod=1e9+7,inc=mod+1>>1;
int n,m,a[N],s[N],c[N],bas[2][N<<1],*f=bas[0]+N,*g=bas[1]+N;

ll ksm(ll a,ll b){
    ll t=1;
    for(;b;b>>=1,a=a*a%mod)
        if(b&1) t=t*a%mod;
    return t;
}

ll Mod(ll x){return x>=mod?x-mod:x;}

void Add(int &x,int y){
    x+=y;
    if(x>=mod) x-=mod;
}

void solve(){
    n=read();m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=m;i++) s[i]=read();
    if(n==1){
        bool fl=0;
        for(int i=1;i<=m;i++)
            if(s[i]){
                puts("0");
                return;
            }
        puts("1");
        return ;
    }
    for(int i=1;i<=m;i++) c[i]=0;
    for(int i=1;i<=n;i++) c[a[i]]++;
    for(int i=1;i<=m;i++) s[i]=s[i]-(c[i]&1)+2&1;
    for(int i=-n;i<=n;i++) f[i]=0;
    f[0]=1;
    for(int k=1;k<=m;k++){
        for(int i=-n;i<=n;i++) g[i]=0;
        for(int i=-n;i<=n;i++)
            if(f[i]){
                Add(g[i+c[k]],f[i]);
                if(s[k]) Add(g[i-c[k]],mod-f[i]);
                else Add(g[i-c[k]],f[i]);
            }
        swap(f,g);
    }
    ll ans=0;
    for(int i=-n;i<=n;i++) (ans+=ksm(Mod(i+mod),n-2)*f[i])%=mod;
    (ans*=ksm(inc,m))%=mod;
    printf("%lld\n",ans);
}

int main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    int T=read();
    while(T--) solve();
    return 0;
}

CF2147H Maxflow GCD Coloring

题面:

定义一个无向图是好的当且仅当存在 \(d\geqslant2\)\(d\) 整除所有 maxflow\((u,v)\)

现给定一张 \(n\) 个点无向图,求一个颜色数最少得染色方案使得任意一种颜色的导出子图都是好的。\(1\leq n\leq 50,1\leq m\leq \frac{n(n-1)}2\)

题解:

首先最大流等于最小割。其次答案的颜色个数是 1 或 2。

对于答案为 1,直接使用最小割树进行判断。

否则考虑构造每个导出子图都存在符合条件的 \(d=2\)

先去掉所有偶数边,如果图中每个点度数都是偶数,则任意两点最小割也是偶数。

证明考虑对于任意 \(S\subseteq V\)\(T=V/S\)

\(S\)\(T\) 之间的边数 \(=\) \(S\) 中所有点的度数之和(偶数)\(-\) 端点都在 \(S\) 中的边数 \(\times 2\) (偶数)= 偶数

所以 \(S,T\) 之间的割的权值是偶数条奇数边为偶数。

所以现在我们需要给每个点染色使得每种颜色导出子图所有点度数都是偶数。

考虑增量构造,每次加入一个点并决定其颜色,设两种颜色的点集分别为 \(A, B\)

先通过递归,每次删除一个奇数度数的点 \(u\),同时对于所有点对 \((v, w)\) 连边,其中 \(v, w\) 都是 \(u\) 的邻居,直到最后剩下的点全为偶数度数(显然至多删成一个点就不会再删了),将它们全部加入点集 \(A\)

现在考虑 \(A,B\) 点集内的所有点度数都是偶数,如何加入点 \(u\) 然后删除递归时添加的边使得 \(A,B\) 点集内的点度数都是偶数,由于点 \(u\) 的邻居有奇数个,则必然有一个点集中包含奇数个 \(u\) 的邻居,设该集合为 \(S\),另一个为 \(T\)。我们应当把 \(u\) 加入 \(T\)

因为原本 \(S, T\) 都满足其中所有点的度数均为偶数,而在加入 \(u\) 之后,我们需要删除所有在这时删除的边。此时度数会发生变化的点只有 \(u\)\(u\) 的邻居,对于 \(u\) 而言,它显然只能加入 \(T\)

对于 \(u\) 的所有邻居:

  • 对于 \(S\),其中有奇数个点是 \(u\) 的邻居,由于 \(u\) 没有加入 \(S\)\(S\) 中所有 \(u\) 的每个邻居的度数减少了 \(S\)\(u\) 的邻居数 \(-1\),这无疑是一个偶数。

  • 对于 \(T\),同上分析,所有 \(u\) 的邻居的度数减少了一个奇数,其奇偶性发生了改变,然而 \(u\) 加入之后每个 \(u\) 的邻居多了一条边,度数又变回来了。

所上述构造方案是合法的。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=55,M=1230;
const ll inf=1e18+7;
int n,m,U[M],V[M],W[M],cnt,e[N][N];

namespace flow{
	int E[N],head[N],dep[N],p[N],S,T,ids[N],tmp[N];
	struct edge{
		int v,nxt,w;
	}e[M<<1];
	
	void add(int u,int v,int w){
		e[++cnt]={v,head[u],w};
		head[u]=cnt;
		e[++cnt]={u,head[v],w};
		head[v]=cnt;
	}
	
	bool bfs(){
		for(int i=1;i<=n;i++) E[i]=head[i],dep[i]=0;
		queue<int>q;
		q.push(S);dep[S]=1;
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(int i=head[x],v;i;i=e[i].nxt){
				v=e[i].v;
				if(e[i].w&&!dep[v]){
					dep[v]=dep[x]+1;
					q.push(v);
					if(v==T) return 1;
				}
			}
		}
		return 0;
	}
	
	ll dfs(int x,ll W){
		if(x==T) return W;
		ll now=0;
		for(int i=E[x],v;i&&now<W;i=e[i].nxt){
			E[x]=i;v=e[i].v;
			if(e[i].w&&dep[v]==dep[x]+1){
				ll tmp=dfs(v,min<ll>(W-now,e[i].w));
				if(!tmp) dep[v]=-1;
				else{
					now+=tmp;
					e[i].w-=tmp;
					e[i^1].w+=tmp;
					if(now==W) return now;
				}
			}
		}
		return now;
	}
	
	ll dinic(){
		cnt=1;
		for(int i=1;i<=n;i++) head[i]=0;
		for(int i=1;i<=m;i++) add(U[i],V[i],W[i]);
		ll ans=0,x=0;
		while(bfs()) while(x=dfs(S,inf)) ans+=x;
		return ans;
	}
	
	void bfs(int s){
		for(int i=1;i<=n;i++) ids[i]=0;
		queue<int>q;
		ids[s]=s; q.push(s);
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(int i=head[x],v;i;i=e[i].nxt){
				v=e[i].v;
				if(e[i].w&&ids[v]!=s){
					ids[v]=s;
					q.push(v);
				}
			}
		}
	}
	
	ll solve(int l,int r){
		if(l==r) return 0;
		S=p[l];T=p[l+1];
		ll ans=dinic();
		bfs(S);
		int L=l,R=r;
		for(int i=l;i<=r;i++)
			if(ids[p[i]]==ids[S]) tmp[L++]=p[i];
			else tmp[R--]=p[i];
		for(int i=l;i<=r;i++) p[i]=tmp[i];
		return __gcd(ans,__gcd(solve(l,L-1),solve(R+1,r)));
	}
	
	ll solve(){
		for(int i=1;i<=n;i++) p[i]=i;
		return solve(1,n);
	}
}

bool vis[N];

void solve(vector<int>&A,vector<int>&B){
	for(int i=1;i<=n;i++){
		if(vis[i]) continue;
		bool o=0;
		for(int j=1;j<=n;j++)
			if(!vis[j]&&e[i][j]) o^=1;
		if(o){
			vis[i]=1;
			for(int x=1;x<=n;x++)
				for(int y=x+1;y<=n;y++)
					if(e[i][x]&&e[i][y]&&!vis[x]&&!vis[y]){
						e[x][y]^=1;
						e[y][x]^=1;
					}
			solve(A,B);
			bool o=0;
			for(int x:A)
				if(e[i][x]) o^=1;
			if(o) B.push_back(i);
			else A.push_back(i);
			return;
		}
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) A.push_back(i);
}

void solve(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		U[i]=read();
		V[i]=read();
		W[i]=read();
	}
	if(m==0||flow::solve()>1){
		puts("1"); printf("%d\n",n);
		for(int i=1;i<=n;i++) printf("%d ",i); puts("");
		return ;
	}
	puts("2");
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) e[i][j]=0;
	for(int i=1;i<=m;i++)
		if(W[i]&1) e[U[i]][V[i]]=e[V[i]][U[i]]=1;
	for(int i=1;i<=n;i++) vis[i]=0;
	vector<int>A,B;
	solve(A,B);
	printf("%d\n",A.size());
	for(int x:A) printf("%d ",x);puts("");
	printf("%d\n",B.size());
	for(int x:B) printf("%d ",x);puts("");
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2174D Secret Message

题面:

给定一张 \(n\) 个点 \(m\) 条边的图,选出其中的 \(n-1\) 条边使得它不构成一棵树,且边权和最小。\(n\leq 2\times 10^5\)

题解:

如果最小 \(n-1\) 条不是树则这就是答案。

否则考虑枚举一条边替换,将前 \(n-1\) 条建树,枚举一条边 \((u,v)\) 删去一条 \(u\)\(v\) 路径之外的最大权边然后加入这个边。这个维护方式有很多。例如可以每次求路径最大值 \(mx\),将路径边全部减去 \(mx\),求全局最大值,然后还原边权。可以树剖线段树实现。

对于改变两条边及以上的,只有删去 \(n-1,n-2\) 加入 \(n,n+1\) 可能是答案。

考虑这个方案如果不是树,则其他方案都不如这个优。

如果是树,则 \(n-2,n-1\) 一定一个在 \((u_n,v_n)\) 路径,一个在 \((u_{n+1},v_{n+1})\) 路径上,且不再其交集上。此时一定可以去掉 \(n-1,n-2\) 其中一条并加入 \(n,n+1\) 其中一条使得它不是树,这比此方案更优秀。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5;
const ll inf=1e18+7;
int dep[N],n,m,dfn[N],tim,top[N],siz[N],id[N],son[N],fat[N],val[N];
vector<int>e[N];
struct EDGE{
	int u,v,w;
}edge[N];
struct Tree{
	int v,tag;
}t[N<<2];

namespace dsu{
	int fa[N];
	
	void clear(int n){
		for(int i=1;i<=n;i++) fa[i]=i;
	}
	
	int find(int x){
		if(fa[x]==x) return x;
		else return fa[x]=find(fa[x]);
	}
	
	void merge(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx!=fy) fa[fx]=fy;
	}
	
	int get(int n){
		int cnt=0;
		for(int i=1;i<=n;i++) cnt+=(fa[i]==i);
		return cnt;
	}
}

namespace LCAF{
	int dfn[N],lgn[N<<1],tim,st[22][N<<1];
	
	void dfs(int x,int fa){
		st[0][++tim]=x;
		dfn[x]=tim;
		for(int v:e[x])
			if(v!=fa){
				dep[v]=dep[x]+1;
				dfs(v,x);
				st[0][++tim]=x;
			}
	}
	
	int cmin(int x,int y){
		return dep[x]<dep[y]?x:y;
	}
	
	int LCA(int x,int y){
		int l=dfn[x],r=dfn[y];
		if(l>r) swap(l,r);
		int mn=lgn[r-l+1];
		return cmin(st[mn][l],st[mn][r-(1<<mn)+1]);
	}
	
	void init(){
		tim=0; dfs(1,0);
		for(int i=2;i<=tim;i++) lgn[i]=lgn[i>>1]+1;
		for(int j=1;j<=lgn[tim];j++)
			for(int i=1;i+(1<<j)-1<=tim;i++)
				st[j][i]=cmin(st[j-1][i],st[j-1][i+(1<<j-1)]);
	}
}
using LCAF::LCA;

void pushup(int s){
	t[s].v=max(t[s<<1].v,t[s<<1|1].v);
}

void upd(int s,int v){
	t[s].v+=v;
	t[s].tag+=v;
}

void pushdown(int s){
	if(t[s].tag){
		upd(s<<1,t[s].tag);
		upd(s<<1|1,t[s].tag);
		t[s].tag=0;
	}
}

void build(int s,int l,int r){
	t[s].tag=0;
	if(l==r){
		t[s].v=val[id[l]];
		return ;
	}
	int mid=l+r>>1;
	build(s<<1,l,mid);
	build(s<<1|1,mid+1,r);
	pushup(s);
}

void update(int s,int l,int r,int L,int R,int v){
	if(L<=l&&r<=R){
		upd(s,v);
		return ;
	}
	int mid=l+r>>1;
	pushdown(s);
	if(L<=mid) update(s<<1,l,mid,L,R,v);
	if(R>mid) update(s<<1|1,mid+1,r,L,R,v);
	pushup(s);
}

int query(int s,int l,int r,int L,int R){
	if(L<=l&&r<=R) return t[s].v;
	pushdown(s);
	int mid=l+r>>1;
	if(R<=mid) return query(s<<1,l,mid,L,R);
	if(L>mid) return query(s<<1|1,mid+1,r,L,R);
	return max(query(s<<1,l,mid,L,R),query(s<<1|1,mid+1,r,L,R));
}

void dfs1(int x,int fa){
	siz[x]=1;son[x]=0;
	for(int v:e[x])
		if(v!=fa){
			dep[v]=dep[x]+1;
			fat[v]=x;
			dfs1(v,x);
			siz[x]+=siz[v];
			if(siz[v]>siz[son[x]]) son[x]=v;
		}
}

void dfs2(int x,int t){
	top[x]=t;dfn[x]=++tim;id[tim]=x;
	if(!son[x]) return ;
	dfs2(son[x],t);
	for(int v:e[x])
		if(v!=fat[x]&&v!=son[x]) dfs2(v,v);
}

void update(int x,int y,int v){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		update(1,1,n,dfn[top[x]],dfn[x],v);
		x=fat[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	if(dfn[x]+1<=dfn[y]) update(1,1,n,dfn[x]+1,dfn[y],v);
}

ll query(int x,int y){
	ll ans=-inf;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=max<ll>(ans,query(1,1,n,dfn[top[x]],dfn[x]));
		x=fat[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	if(dfn[x]+1<=dfn[y]) ans=max<ll>(ans,query(1,1,n,dfn[x]+1,dfn[y]));
	return ans;
}

void solve(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read(),w=read();
		edge[i]={u,v,w};
	}
	sort(edge+1,edge+1+m,[&](EDGE x,EDGE y){ return x.w<y.w; });
	dsu::clear(n);
	for(int i=1;i<n;i++) dsu::merge(edge[i].u,edge[i].v);
	ll sum=0;
	for(int i=1;i<n;i++) sum+=edge[i].w;
	if(dsu::get(n)!=1){
		printf("%lld\n",sum);
		return ;
	}
	for(int i=1;i<n;i++){
		int u=edge[i].u,v=edge[i].v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	tim=0;
	dfs1(1,0); dfs2(1,1);
	val[1]=0;
	for(int i=1;i<n;i++){
		int u=edge[i].u,v=edge[i].v,w=edge[i].w;
		if(dep[u]>dep[v]) swap(u,v);
		val[v]=w;
	}
	build(1,1,n);
	ll ans=inf;
	for(int i=n;i<=m;i++){
		int u=edge[i].u,v=edge[i].v,w=edge[i].w;
		int mx=query(u,v);
		update(u,v,-mx);
		if(t[1].v>0) ans=min(ans,sum-t[1].v+w);
		update(u,v,mx);
	}
	if(n>=3&&m>=n+1)
		ans=min(ans,sum-edge[n-1].w-edge[n-2].w+edge[n].w+edge[n+1].w);
	if(ans>inf/2) puts("-1");
	else printf("%lld\n",ans);
	for(int i=1;i<=n;i++) e[i].clear();
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2097F Lost Luggage

题面:

\(n\) 个机场排成环,进行 \(m\) 天实验,在第一天午夜前,第 \(j\) 个机场有 \(s_j\) 件遗失行李。第 \(i\) 天会发生一下事件:

  • 早晨,同时起飞 \(2n\) 个航班,包括 \(n\) 个第一类航班和 \(n\) 个第二类航班:
    • 第一类第 \(j\) 个航班飞往 \(j\) 的前一个机场,最多可运输 \(a_{i,j}\) 件遗失行李;
    • 第二类第 \(j\) 个航班飞往 \(j\) 的后一个机场,最多可运输 \(c_{i,j}\) 件遗失行李;
  • 下午,机场会进行遗失行李检查。如果当天航班起飞后,第 \(j\) 个机场剩余 \(x\) 件行李且 \(x \ge b_{i, j}\),则至少会有 \(x - b_{i, j}\) 件行李被找到,不再视为遗失;
  • 晚上,当天所有 \(2n\) 个航班结束,运输的遗失行李抵达对应机场。

对于每个 \(k\)\(1\)\(m\),求出在前 \(k\) 天的检查中可能未被找到的遗失行李最大数量。注意每个 \(k\) 的计算都是独立的。\(3\leq n\leq 12,1\leq m\leq 2000\)

题解:

考虑怎么对于一个固定的 \(k\) 求出答案。

考虑建图网络流,对每天的每个机场都建出一个点 \((i,j)\)

  • \(S\overset{s_j}{\longrightarrow} (1,j)\)\((m,j)\overset{inf}{\longrightarrow} T\)
  • \((i,j)\overset{b_{i,j}}{\longrightarrow} (i+1,j)\)\((i,j)\overset{a_{i,j}}{\longrightarrow}(i+1,j-1)\)\((i,j)\overset{c_{i,j}}{\longrightarrow}(i+1,j+1)\)

答案是最大流。

考虑最大流转最小割求解。
\(f_{i,S}\) 表示考虑到第 \(i\) 行所有点 \((i,j)\)\(S\) 集合内的点以都被割掉。转移考虑枚举 \(i+1\) 行被割掉的点集 \(T\),方程为:

\[f_{i,S}+\sum_{u\not\in S,v\in T}e_{u,v}\to f_{i+1,T} \]

直接状压复杂度 \(O(4^n)\)。每个点只和前后三个点相关,枚举第一个点的状态转为轮廓线复杂度 \(O(2^n)\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=15,M=(1<<12)+5;
const ll inf=1e18+7;
int n,m,S,r[N],l[N];
ll a[N],b[N],c[N],f[M],g[M][2],h[M][2],F[M];

void clear(ll g[M][2]){
	for(int s=0;s<1<<n;s++) g[s][0]=g[s][1]=inf;
}

void cmin(ll &x,ll y){x=min(x,y);}

void calc(int o){
	clear(g);
	for(int s=o;s<=S;s+=2) g[s][s>>n-1&1]=f[s];
	for(int i=0;i<n-1;i++){
		clear(h);
		for(int s=0;s<=S;s++)
			for(int x:{0,1}) if(g[s][x]<inf/2){
				cmin(h[s|1<<i][s>>i&1],g[s][x]);
				cmin(h[s&(S^1<<i)][s>>i&1],g[s][x]+(s>>i+1&1)*a[r[i]]+(s>>i&1)*b[i]+x*c[l[i]]);
			}
		for(int s=0;s<=S;s++){
			g[s][0]=h[s][0];
			g[s][1]=h[s][1];
		}
	}
	for(int s=0;s<=S;s++)
		for(int x:{0,1}) if(g[s][x]<inf/2){
			cmin(F[s|1<<n-1],g[s][x]);
			cmin(F[s&(S^1<<n-1)],g[s][x]+o*a[0]+(s>>n-1&1)*b[n-1]+x*c[n-2]);
		}
}

void solve(){
	n=read();m=read();
	for(int i=0;i<n;i++) a[i]=read();
	S=(1<<n)-1;
	for(int s=0;s<=S;s++){
		f[s]=0;
		for(int i=0;i<n;i++)
			if(!(s>>i&1)) f[s]+=a[i];
	}
	for(int i=0;i<n;i++) l[i]=i-1,r[i]=i+1;
	l[0]=n-1;r[n-1]=0;
	for(int T=1;T<=m;T++){
		for(int i=0;i<n;i++) a[i]=read();
		for(int i=0;i<n;i++) b[i]=read();
		for(int i=0;i<n;i++) c[i]=read();
		for(int s=0;s<=S;s++) F[s]=inf;
		calc(0);calc(1);
		for(int s=0;s<=S;s++) f[s]=F[s];
		printf("%lld\n",f[0]);
	}
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2178I Numbers or Fireworks

题面:

平面上有 \(n\) 个整点,每次选取其一个真子集 \(T\),此时每个点的价值定义为 \(T\) 中与其距离为 \(\sqrt k\) 的点的个数加一,\(T\) 的价值定义为不在 \(T\) 中的所有点价值乘积,求所有 \(T\) 的价值和。\(n\leq 31\)

题解:

将所有满足 \((x_i-x_j)^2+(y_i-y_j)^2=k\)\((i,j)\) 连边,得到的图是二分图。

证明:
二分图等价于没有奇环。
假如奇环上有 \(m\) 个点,满足 \((x_i-x_{i+1})+(y_i-y_{i+1})=k\)
考虑一个数和它的平方奇偶性相同,所以上述式子成立的一个必要条件为 \(x_i-x_{i+1}+y_i-y_{i+1}\equiv k(\!\bmod 2)\)
\(m\) 个式子加和,由于 \(m\) 为奇数,所以得到 \(0\equiv k(\!\bmod 2)\)。如果 \(k\) 是奇数则矛盾。
如果 \(k\) 是偶数,则将坐标系旋转 \(45^\circ\),那么新的 \(k\) 变成 \(\frac k 2\),要求仍然是 \(k\) 是偶数,但总有时刻 \(k\) 变成奇数,所以总会矛盾。

考虑 \(T\) 价值的组合意义,每个没有被选中的点选择邻域中的一个特殊点匹配,也可以不匹配,的方案数。

记左部点为 \(L\),右部点为 \(R\),钦定 \(|L|\leq |R|\),所以 \(|L|\leq \frac n 2\)。枚举 \(A\subseteq L\),为 \(L\) 中特殊点的点集,令 \(T_R\) 为被选中的右部点集合。记 \(f_s(s\subseteq L/A)\) 表示 \(s\) 中的点已经确定了与哪一个特殊点匹配,到最后还没有匹配的点就代表不匹配。

枚举每个右部点 \(u\)

  • \(u\notin T_R\):此时 \(u\) 不会对 \(L\) 产生贡献。\(f_s(1+c_u)\to f_s'\)\(c_u\)\(u\)\(A\) 相邻的点数。
  • \(u\in T_R\):此时 \(u\) 可能覆盖与其相邻的左部点,设 \(u\)\(B\) 的交集为 \(t\)。左部点中可能有某些 \(t\) 集合内的点匹配 \(u\) 点,有 \(\sum_{i\subseteq s,s\subseteq i\cup t}f_i\to f_s'\)

第一个转移是容易的。第二个转移是高位前缀和,具体的,\(i\subseteq s\) 就是普通的高位前缀和,但是考虑还有 \(s\subseteq i\cup t\),即如果 \(t\) 这一位是 \(0\),则 \(s\)\(i\) 的这一位必须是一样的,所以只对 \(t\) 中所有为 \(1\) 的位做高维前缀和表示这一位上可以随便选即可。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=35,mod=998244353;
int n,K,x[N],y[N],id[N],ind[N],col[N];
vector<int>e[N],a,b;
ll f[1<<17],g[1<<17];

ll Mod(ll x){return x>=mod?x-mod:x;}
void Add(ll &x,ll y){x=Mod(x+y);}

void dfs(int x){
	if(col[x]==1) a.push_back(x);
	else b.push_back(x);
	for(int v:e[x])
		if(!col[v]){
			col[v]=col[x]^3;
			dfs(v);
		}
}

void solve(){
	n=read();K=read();
	for(int i=1;i<=n;i++) col[i]=0;
	for(int i=1;i<=n;i++){
		x[i]=read();
		y[i]=read();
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])==K) e[i].push_back(j);
	a.clear(); b.clear();
	for(int i=1;i<=n;i++)
		if(!col[i]){
			col[i]=1;
			dfs(i);
		}
	if(a.size()>b.size()) swap(a,b);
	ll ans=0;
	for(int A=0;A<1<<a.size();A++){
		int sz=0;
		for(int i=1;i<=n;i++) ind[i]=id[i]=0;
		for(int i=0;i<a.size();i++)
			if(!(A>>i&1)) id[a[i]]=++sz;
			else for(int v:e[a[i]]) ind[v]++;
		for(int s=0;s<1<<sz;s++) f[s]=0;
		f[0]=1;
		for(int i=0;i<b.size();i++){
			for(int s=0;s<1<<sz;s++) g[s]=f[s];
			for(int s=0;s<1<<sz;s++) (f[s]*=(1+ind[b[i]]))%=mod;
			int t=0;
			for(int v:e[b[i]]) if(id[v]) t|=1<<id[v]-1;
			for(int i=0;i<sz;i++) if(t>>i&1) 
                for(int s=0;s<1<<sz;s++) if(s>>i&1) Add(g[s],g[s^1<<i]);
			for(int s=0;s<1<<sz;s++) Add(f[s],g[s]);
		}
		for(int s=0;s<1<<sz;s++) Add(ans,f[s]);
	}
	for(int i=1;i<=n;i++) e[i].clear();
	printf("%lld\n",Mod(ans+mod-1));
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2183E LCM is Legendary Counting Master

题面:

给定一个长为 \(n\) 值域为 \(m\) 的序列 \(a\),替换序列中的所有 \(0\)\([1,m]\) 中的某个数,使得序列是好的,有多少种方案?

定义一个序列是好的当且仅当一下条件都成立:

  • \(a\) 单调递增。
  • \(\sum_{i=1}^n \dfrac 1 {{\rm lcm}(a_i,a_{i+1})}\geq 1\)。(\(a_{n+1}=a_1\)

\(2\leq n\leq m\leq 3000\)

题解:

\(a_i<a_{i+1}\) 时,一定有 \(\gcd(a_i,a_{i+1})\leq a_{i+1}-a_i\)
同时还有 \(\gcd(a_i,a_{i+1})\leq \min(a_i,a_{i+1})\)

\[\sum_{i=1}^n \dfrac 1 {{\rm lcm}(a_i,a_{i+1})}=\sum_{i=1}^n\dfrac {\gcd(a_i,a_{i+1})}{a_ia_{i+1}}\leq \sum_{i=1}^{n-1}\dfrac{a_{i+1}-a_i}{a_ia_{i+1}}+\dfrac {a_1}{a_1a_n} \]

有经典裂项相消 \(\dfrac 1 x-\dfrac 1 y=\dfrac {y-x}{xy}\)

\[\sum_{i=1}^{n-1}\dfrac{a_{i+1}-a_i}{a_ia_{i+1}}+\dfrac {a_1}{a_1a_n}=\sum_{i=1}^{n-1}(\dfrac 1 {a_i}-\dfrac 1 {a_{i+1}})+\dfrac 1 {a_n}=\dfrac 1 {a_1}\leq 1 \]

所以序列满足条件充要为 \(a_1=1,\forall i\in[1,n-1]\gcd(a_i,a_{i+1})=a_{i+1}-a_i\)

\(\gcd(x,y)=y-x\) 表示 \((y-x)|x\) 即存在 \(d|x\) 使得 \(y=x+d\),这样的 \((x,y)\) 共有 \(m\ln m\) 个,容易 \(O(nm\ln m)\) dp。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=3005,mod=998244353;
int n,m,a[N],f[N][N];
vector<int>vec[N];

void Add(int &x,int y){
	x+=y;
	if(x>=mod) x-=mod;
}

void init(int n){
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j+=i) vec[j].push_back(i);
}

void solve(){
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) f[i][j]=0;
	if(a[1]!=1&&a[1]!=0){
		puts("0");
		return ;
	}
	f[1][1]=1;
	for(int i=1;i<n;i++){
		if(a[i+1]){
			for(int j=1;j<a[i+1];j++)
				if(f[i][j]&&__gcd(j,a[i+1])==a[i+1]-j) Add(f[i+1][a[i+1]],f[i][j]);
		}
		else{
			for(int j=1;j<=m;j++) if(f[i][j]) 
				for(int x:vec[j]){
					if(j+x>m) break;
					Add(f[i+1][j+x],f[i][j]);
				}
		}
	}
	int ans=0;
	for(int j=1;j<=m;j++) Add(ans,f[n][j]);
	printf("%d\n",ans);
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	init(3000);
	int T=read();
	while(T--) solve();
	return 0;
}

CF2190D Prufer Vertex

题面:

对于一棵无根树 \(T\),每次删除其编号最小的叶子,知道还剩两个点的时候,此时肯定有一个是 \(n\),设另一个点是 \(v\),记 \(P(T)=v\)

给你一个 \(n\) 个点 \(m\) 条边的森林,对于每个 \(1\leq v < n\),求将他们连成一棵树 \(T\) 的方案数使得 \(P(T)=v\)\(n\leq 2\times 10^5\)

题解:

考虑 \(n-1\) 什么时候被删除,由于它足够大,所以它被删除当且仅当目前只有它和 \(n\) 是叶子,即还剩一条以 \(n-1\)\(n\) 为两端的链,此时过后一定是从 \(n-1\) 沿着链一直删到 \(n\)。所以 \(P(T)\) 就是 \(n\)\(n-1\) 这条链上的第二个点。

枚举 \(P(T)=v\),如果 \(v\) 不在 \(n\) 的连通块中就肯定要先连上两个连通块。然后考虑 \(n-1\) 如果和 \(n\) 联通那么随意生成树都不会改变最终答案;如果不是一个那么最后 \(n-1\) 的联通块连到 \(n\) 连通块上的每个点都是可以的,而连到其上的一个子树的点是合法的,假设连通块大小是 \(C\),合法子树大小是 \(K\),那么答案就是随意生成树个数乘 \(\frac K C\)

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=2e5+5,mod=998244353;
int fat[N],siz[N],n,m;
vector<int>S,e[N];
ll ans[N];

namespace dsu{
	int fa[N],siz[N];
	
	void init(int n){
		for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
	}
	
	int find(int x){
		if(fa[x]==x) return x;
		else return fa[x]=find(fa[x]);
	}
	
	void merge(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx==fy) return ;
		fa[fx]=fy;siz[fy]+=siz[fx];
	}
	
	bool sam(int x,int y){
		return find(x)==find(y);
	}
	
	vector<int>get(){
		vector<int>vec;
		for(int i=1;i<=n;i++)
			if(fa[i]==i) vec.push_back(siz[i]);
		return vec;
	}
	
	int ask(int x){
		return siz[find(x)];
	}
}

void dfs(int x,int fa){
	siz[x]=1;
	for(int v:e[x])
		if(v!=fa){
			fat[v]=x;
			dfs(v,x);
			siz[x]+=siz[v];
		}
}

ll ksm(ll a,ll b){
	ll t=1;
	for(;b;b>>=1,a=a*a%mod)
		if(b&1) t=t*a%mod;
	return t;
}

void solve(){
	n=read();m=read();
	for(int i=1;i<=n;i++) e[i].clear();
	dsu::init(n);
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
		dsu::merge(u,v);
	}
	S=dsu::get();
	for(int i=1;i<n;i++) ans[i]=0;
	fat[n]=0; dfs(n,0);
	if(dsu::sam(n,n-1)){
		int x=n-1;
		while(fat[x]!=n) x=fat[x];
		if(S.size()==1) ans[x]=1;
		else{
			ll res=ksm(n,S.size()-2);
			for(int x:S) (res*=x)%=mod;
			ans[x]=res;
		}
		for(int i=1;i<n;i++) printf("%lld ",ans[i]);puts("");
		return ;
	}
	ll fac=1;
	for(int x:S) (fac*=x)%=mod;
	for(int v:e[n])
		ans[v]=fac*ksm(n,S.size()-2)%mod*siz[v]%mod*ksm(siz[n],mod-2)%mod;
	for(int x=1;x<n;x++){
		if(dsu::sam(n,x)) continue;
		if(dsu::sam(x,n-1)){
			if(S.size()==2) ans[x]=1;
			else{
				int u=dsu::ask(n-1),v=siz[n];
				ans[x]=fac*ksm(u,mod-2)%mod*ksm(v,mod-2)%mod*(u+v)%mod*ksm(n,S.size()-3)%mod;
			}
		}
		else{
			int u=dsu::ask(x),v=siz[n];
			ans[x]=fac*ksm(u,mod-2)%mod*ksm(v,mod-2)%mod*ksm(n,S.size()-3)%mod*u%mod;
		}
	}
	for(int i=1;i<n;i++) printf("%lld ",ans[i]);puts("");
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--) solve();
	return 0;
}
posted @ 2025-05-29 20:18  programmingysx  阅读(37)  评论(0)    收藏  举报
Title