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;
}
posted @ 2025-05-29 20:18  programmingysx  阅读(28)  评论(0)    收藏  举报
Title