Atcoder 记录

Atcoder 记录

ARC193C Grid Coloring 3

题面:

给你一个 \(n\times m\) 的网格初始全没有被染色,每次可以选择 \((x,y,c),1\leq x\leq n,1\leq y\leq m,1\leq c\leq C\) 将,第 \(x\) 行和第 \(y\) 列全部染成第 \(c\) 种颜色(覆盖之前的颜色),可以操作任意次,问最终有多少种不同的染色方案。\(n,m<=400,C<=1e9\)

题解:

因为后面染色能覆盖前面的颜色,所以要将操作序列时光倒流,现在变成每次染还没有颜色的格子,有多少种方案。

在第一次操作(翻转操作序列前的最后一次操作)染了一个十字之后,钦定后面的每一次操作中心 \((x,y)\) 必须已经被染过色,那第 \(x\) 行和第 \(y\) 列中必有一个已经全部染色了,那么操作就变成了每次给一行或者一列染一个颜色(分别记为 \(A,B\) 操作),显然最终的答案是不变的。

但这时直接枚举每次操作是啥仍然不太可做,原因是对于操作序列中两次连续的 \(A\) 操作或 \(B\) 操作,交换他们的操作顺序不影响答案,所以会算重。由于 \(A\)\(A\) 之间互不干扰,\(B\)\(B\) 之间也是,这启发我们每次可以同时操作操作序列中一段极长的 \(A\)\(B\),显然这样转化后答案仍然是对的,这就好做了。

将第一段特殊处理,枚举最终有 \(i\)\(j\) 列都是第一次操作的颜色,方案数是 \(C\binom{n}{i}\binom{m}{j}\),然后进行 \(dp\)
先设一个大概的雏形 \(f_{n,m,* }\) 表示给 \(n\times m\) 的网格染色的答案。
在此之后的下一个极长段中,显然不能有一行或一列的颜色和第一次操作的颜色一样,于是我们将 \(dp\) 改成 \(f_{n,m,t,* }\) 表示有 \(t\) 中颜色不能作为一行或一列全部相同时的颜色,显然 \(t\in\{0,1\}\)
考虑下一个极长段,不妨设其是 \(A\) 操作的极长段,设此次操作了 \(r\) 行,方案数是 \((C-t)^r\),后续操作中不再有 \(t\) 的限制,所以从 \(f_{n-r,m,0,* }\) 转移过来。

但是为了保证这一段 \(A\) 是极长的,要求剩下的 \(n-r\) 行中不能有某一行的颜色全部相同,列同理,所以要记 \(f_{n,m,t,x,y}\)\(x,y\) 分别表示当前是否限定每一行(列)的颜色不能完全相同。发现 \(x,y\) 两个限制最多同时存在一个(如果同时存在两个的话就没有能操作的东西了)所以我们可以只记一个(下面只记列是否有限制),通过交换行列数来限定是行还是列,例如上边的转移被写作 \(f_{n,m,t,0}\leftarrow f_{m,n-r,0,1}\)

然后是 \(f_{n,m,t,1}\) 的转移,要求是没有一列的颜色全相同。分两类讨论,如果这 \(r\) 行本身的颜色就有不同的,肯定符合要求,方案数是 \((C-t)^r-(C-t)\),从 \(f_{m,n-r,0,1}\) 转移过来。如果这 \(r\) 行都相同,那要求这列其余部分不能全都是当前的这种颜色,这个状态就是 \(f_{m,n-r,1,1}\) 的限制,方案数是 \((C-t)\)

最后答案是 \(f_{n-i,m-j,1,0}+f_{m-j,n-i,1,1}\) 即考虑执行完最终第一次操作的那些之后的第一个极长段时 \(A\) 还是 \(B\)

代码
#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=405,mod=998244353;
ll fac[2][N],f[N][N][2][2],C[N][N],K;
int n,m;

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

ll dfs(int n,int m,int t,int c){
	if(f[n][m][t][c]>=0) return f[n][m][t][c];
	ll res=fac[t][n];
	if(c) Add(res,mod-(K-t));
	for(int i=1;i<n;i++){
		ll tmp=0;
		if(!c) Add(tmp,fac[t][i]*dfs(m,n-i,0,1)%mod);
		else{
			Add(tmp,(K-t)*dfs(m,n-i,1,1)%mod);
			Add(tmp,(fac[t][i]-(K-t)+mod)%mod*dfs(m,n-i,0,1)%mod);
		}
		Add(res,tmp*C[n][i]%mod);
	}
	return f[n][m][t][c]=res;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();K=read();
	int S=max(n,m);
	K%=mod;
	if(K==0) K=mod;
	for(int i=0;i<=S;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++){
			C[i][j]=C[i-1][j];
			Add(C[i][j],C[i-1][j-1]);
		}
	}
	fac[0][0]=fac[1][0]=1;
	for(int i=1;i<=S;i++){
		fac[0][i]=fac[0][i-1]*K%mod;
		fac[1][i]=fac[1][i-1]*(K-1)%mod;
	}
	memset(f,-1,sizeof(f));
	ll ans=1;
	for(int i=1;i<n;i++)
		for(int j=1;j<m;j++)
			Add(ans,C[n][i]*C[m][j]%mod*(dfs(n-i,m-j,1,0)+dfs(m-j,n-i,1,1))%mod);
	(ans*=K)%=mod;
	printf("%lld",ans);
	return 0;
}

ARC193D Magnets

题面:

给两个长为 \(n\)\(01\) 序列 \(A,B\),每次可以选择一个位置让每个 \(1\) 都向选的位置靠近一位,若一个位置上有多个 \(1\) 认为是一个。问最少多少次操作能让 \(A\) 序列和 \(B\) 序列一样,或报告无解。\(n\leq 1e6\)

题解:

观察操作发现,如果我们操作在所有 \(1\) 前或所有 \(1\) 后,可以起到一个平移的效果,即 \(A\) 中所有 \(1\) 的相对位置不变,这启示我们算出 \(A,B\) 的差分数组 \(da,db\)。问题转化为要把 \(A\) 的差分数组变成和 \(B\) 的差分数组一样(删去所有 \(da_i=0\))。

考虑一次操作会导致 \(da\) 发生什么变化:如果操作在两个 \(1\) 中间,会导致 \(da_i-2\)(称为 \(X_i\) 操作);如果操作在某个 \(1\) 上,会导致 \(da_i,da_{i+1}\) 都减少 \(1\)(称为 \(Y_i\) 操作)。特别的,如果操作在第一个或最后一个 \(1\) 上,只会令 \(da_1-1\)\(da_m-1\)

先不考虑这两个特殊的操作,那么每次操作都会让 \(da\) 的总和 \(-2\),操作次数的总和就是 \(\frac{\sum da_i-\sum db_i}{2}\)。我们让 \(da_i\) 依次和 \(db_j\) 匹配,如果 \(da_i>db_j\) 那么我们一直 \(da_i-2\) 直到 \(da_i==db_j+1\) 我们让 \(da_i-1,da_{i+1}-1\),然后匹配 \(da_{i+1},db_{j+1}\),否则如果 \(da_i<db_j\) 就把 \(da_i\) 减成零。如果最后 \(db_j\) 有剩下的没有配上或者 \(da_{m+1}\)\(-1\) 了就不合法,我们就需要通过特殊操做尽量让他合法并保证次数最小。
要求最小次数,那我们只要执行尽量少的特殊操作就行了,因为别的操作都是 \(-2\) 而特殊操作是 \(-1\)

有结论,特殊操做只会在开头和结尾各最多做一次。
如果最开始一下做了两次特殊操作(不妨设是在开头做的),那么可以将其改为一个 \(X\) 操作并向左平移一位,这样更改后没有变化操作数,连绝对位置都没有变化,可以替换。
如果匹配了某个 \(da\)\(db\) 的前缀后 \(da_i\) 现在成了队头并对其做了一次特殊操作,那么考虑对 \(da_{i-1}\) 执行的操作,如果执行了 \(X_{i-1}\),可以替换为 \(Y_{i-1}\) 和对 \(i-1\) 是队头时的特殊操作,如果执行了 \(Y\) 操作类似,这样我们就把 \(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=1e6+5,inf=1e9+7;
int n;
char s[N],t[N];

int solve(int pl,int pr,vector<int>A,vector<int>B){
	if(pl) for(int i=1;i<A.size();i++) A[i]--;
	if(pr) for(int i=0;i<A.size()-1;i++) A[i]++;
	vector<int>dx,dy;
	for(int i=1;i<A.size();i++)
		if(A[i]>A[i-1]) dx.push_back(A[i]-A[i-1]);
	for(int i=1;i<B.size();i++)
		if(B[i]>B[i-1]) dy.push_back(B[i]-B[i-1]);
	if(dy.size()>dx.size()) return inf;
	int j=0,op=0,ans=0;
	for(int i=0;i<dx.size();i++){
		dx[i]-=op;
		int d=dx[i];
		if(j<dy.size()&&dx[i]>=dy[j]){
			d-=dy[j];
			j++;
		}
		ans+=d+1>>1;
		op=d&1;
	}
	if(j<dy.size()||op) return inf;
	return ans+abs(A[0]+ans-B[0])+pl+pr;
}

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=read();
	while(T--){
		n=read();
		scanf("%s",s+1);
		scanf("%s",t+1);
		vector<int>A,B;
		for(int i=1;i<=n;i++){
			if(s[i]=='1') A.push_back(i);
			if(t[i]=='1') B.push_back(i);
		}
		if(B.size()>A.size()){
			puts("-1");
			continue;
		}
		if(A.size()==1){
			printf("%d\n",abs(A[0]-B[0]));
			continue;
		}
		int ans=inf;
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++)
				ans=min(ans,solve(i,j,A,B));
		if(ans>inf/2) puts("-1");
		else printf("%d\n",ans);
	}
	return 0;
}

ARC195E Random Tree Distance

题面:

给定一个长为 \(n-1\) 的序列 \(a_2\sim a_n\),对于一个长为 \(n-1\) 的满足 \(p_i<i\) 的序列 \(p_2\sim p_n\),以 \(p_i\)\(i\) 的父亲 \(a_i\)\(i\)\(p_i\) 之间的边权,构造一棵以 \(1\) 为根的树。
\(Q\) 次询问,每次给出一个 \((u,v)\),问对于所有合法的序列 \(p\),以如上方式构造一棵树,树上 \(u\to v\) 的距离的和是多少。
\(n,Q\leq 2\times 10^5,1\leq a_i\leq 10^9\)

题解:

考虑转成期望计数,显然所有合法的序列 \(p\) 的方案数是 \((n-1)!\)

两点间距离转换成 \(dis(u,v)=dep_u+dep_v-2\times dep_{lca(u,v)}\)

\(E_x\)\(dep_x\) 的期望深度,有转移方程 \(E_x=a_x+\frac{1}{x-1}\sum_{i=1}^{x-1}E_i\)

然后求 \(dep_{lca(u,v)}\) 的期望,不妨设 \(u<v\),则一定有 \(lca(u,v)<u\),考虑暴力跳 \(lca\) 的过程,若 \(p_v>u\) 则和最开始的情况没有区别,若 \(p_v<u\) 则这次跳之后的落点在 \([1,v-1]\) 的概率相等,即发现 \(lca\)\([1,u]\) 之内的概率是相等的。
因此有结论 \(\forall j_1,j_2>i,E(dep_{lca(i,j_1)})=E(dep_{lca(i,j_2)})\),所以 \(E(dep_{lca(u,v)})\)\(v\) 无关。

\(f_i\) 表示 \(E(dep_{lca(i,*)}),*>i\),当 \(lca(i,*)=i\) 时,贡献是 \(E_i\),否则若 \(lca(i,*)=j,j<i\),贡献是 \(f_j\)。所以有转移 \(f_i=\frac{1}{i}(E_i+\sum_{j=1}^{i-1}f_j)\)

最终答案是 \((n-1)!\times (E_u+E_v-2\times f_{\min(u,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,mod=998244353;
int n,Q;
ll inv[N],a[N],f[N],g[N];

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();Q=read();
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=n;i++) a[i]=read();
	ll sum=0;
	for(int i=2;i<=n;i++){
		f[i]=(sum*inv[i-1]+a[i])%mod;
		(sum+=f[i])%=mod;
	}
	sum=0;
	for(int i=2;i<=n;i++){
		g[i]=inv[i]*(sum+f[i])%mod;
		(sum+=g[i])%=mod;
	}
	ll fac=1;
	for(int i=1;i<n;i++) (fac*=i)%=mod;
	for(int i=1;i<=Q;i++){
		int x=read(),y=read();
		if(x>y) swap(x,y);
		printf("%lld\n",fac*(f[x]+f[y]-g[x]*2%mod+mod)%mod);
	}
	return 0;
}

ARC196D Roadway

题面:

\(n\) 个点排成一条线,由 \(n-1\) 条边顺次连接。有 \(m\) 个人,每个人从 \(s_i\) 走到 \(t_i\),初始其体力为 \(0\),经过一条边权为 \(w_i\) 的边后体力会加上 \(w_i\)。每个人都想使得走到 \(t_i\) 时体力是 \(0\) 且每个中间点的体力都是正整数。
\(Q\) 次询问,每次问只考虑 \([L_i,R_i]\) 的人,是否有一种给边赋权的方案使得满足每个人的要求。
\(n,m,Q\leq 4\times 10^5,\ \ |s_i-t_i|>1,\ \ (s_i,t_i)\not= (s_j,t_j)(i\not= j)\) 注意不保证 $s_i<t_i $。

题解:

考虑将每个点设一个势能 \(v_j\),连接 \(i,i+1\) 的边权 \(w_i=v_{i+1}-v_i\)。那么若一个人 \(s_i<t_i\),其限制转化为 \(v_{s_i}=v_{t_i},\forall j\in (s_i,t_i),v_j>v_{s_i}\)。如果 \(s_i>t_i\) 就是 \(v_j<v_{s_i}\)

那如果只有 \(s_i<t_i\) 怎么做?每个 \((s_i,t_i)\) 不能有相交的边,可以包含。形式化的说,对于每个 \((i,j)\) 都要满足以下条件之一:

  • \(t_i\leq s_j\)\(t_j\leq s_i\)
  • \(s_i<s_j<t_j<t_i\)\(s_j<s_i<t_i<t_j\)
    我们称其为条件 \((*)\)

若有一对 \((i,j)\) 不满足 \((*)\),不妨设他们端点从左到右是 \(s_i,s_j,t_i,t_j\) 那根据限制有 \(s_j>s_i=t_i>s_j\to s_j>s_j\),是不合法的。
若都满足 \((*)\),则相离的区间互不干扰,包含的区间大小关系是确定的,所以一定存在解。

如果同时有 \(s_i<t_i\)\(s_j>t_j\)

  • 只考虑 \(s_i<t_i\) 的人要满足 \((*)\)
  • 只考虑 \(s_i>t_i\) 的人要满足 \((*)\)
  • 首先对于任意不同方向的 \((i,j)\)\(s_i\not= t_j\)\(s_j\not= t_i\)

否则在两区间相等的边缘处的势能一定无法满足。
例如说 \(s_i<t_i,s_j>t_j,s_i=t_j\)\(x=s_i=t_j\),那 \(i\) 要求 \(v_{x+1}>v_x\)\(j\) 要求 \(v_{x+1}<v_x\),这是矛盾的。

这三个条件都是合法的必要条件,而他们三个加起来也是充要的。

考虑一个 \(n\) 个点 \(m\) 条边的有向图,每条边从 \(s_i\to t_i\)
对于一个 \(s_i<x<t_i\) 的点 \(x\) 我们称 \(x\) 被这条边覆盖
如果暂时的把每条边看做无向边,则这个图可以切割成若干联通块,每个联通块内的连边一定形如 \(a\gets \cdots \gets d\gets x \to e \to \cdots \to g\)(可能不存在向左或向右的延伸)。我们称它为一条路径。一条路径的最左顶点为 \(l\),最右为 \(r\)
\([l,r]\) 不交的两条路径其势能之间没有限制。
考虑相交的两条路径 \((i,j)\),设 \(l_i<l_j<r_i<r_j\),如果 \(i\) 中覆盖 \(l_j\) 的那条边 \(k\) 满足 \(s_k<t_k\)\(i,j\) 的大小关系是 \(i<j\),否则是 \(i>j\)
假设是 \(i<j\),那么对于 \(j\) 覆盖的某个点 \(x\),若覆盖 \(x\) 的那条边是 \(s_p<x<t_p\) 的,那么根据上述条件的限制,\(i\) 中覆盖这个点的边肯定是 \(s_q<s_p<x<t_p<t_q\),符合 \(i<j\) 的关系,反之亦然。
所以各个路径之间的大小关系可以的到一个 \(DAG\),并以此确定解。

在判断合法时第三个条件容易检查,第一二个条件可以使用 \(bit\) 维护 \(xor-hashing\) 来解决,具体可见代码。
双指针的对于每个 \(r=1\sim i\) 处理最远的合法左端点 \(f_r\),在询问时判断是否 \(l\geq f_r\) 即可。

代码
#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;
mt19937 rnd(time(0));
int n,m,Q;
struct Tree{
	ll c[N];
	void add(int x,ll v){
		while(x<=n){
			c[x]^=v;
			x+=x&-x;
		}
	}
	ll ask(int x){
		ll res=0;
		while(x>0){
			res^=c[x];
			x-=x&-x;
		}
		return res;
	}
}tr[2];
int L[N],R[N],l[N],r[N],op[N],mn[N];
ll val[N];

int main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n=read();m=read();Q=read();
	for(int i=1;i<=m;i++){
		l[i]=read(),r[i]=read();
		op[i]=l[i]>r[i];
		if(l[i]>r[i]) swap(l[i],r[i]);
	}
	for(int i=1;i<=m;i++) val[i]=rnd();
	for(int i=1,j=1;i<=m;i++){
		tr[op[i]].add(l[i],val[i]);
		tr[op[i]].add(r[i],val[i]);
		L[l[i]]++;R[r[i]]++;
		while(L[l[i]]>1||R[r[i]]>1||tr[op[i]].ask(r[i]-1)!=tr[op[i]].ask(l[i])){
			tr[op[j]].add(l[j],val[j]);
			tr[op[j]].add(r[j],val[j]);
			L[l[j]]--; R[r[j]]--;
			j++;
		}
		mn[i]=j;
	}
	while(Q--){
		int l=read(),r=read();
		if(l>=mn[r]) puts("Yes");
		else puts("No");
	}
	return 0;
}

ARC197D Ancestor Relation

题面:

给定一个 \(n\times n\)\(01\) 矩阵 \(a\),如果 \(a_{i,j}\)\(1\) 表示在以 \(1\) 为根的树中 \(i,j\) 有祖先后代关系,反之则没有。问符合条件的树的方案数。\(n\leq 400\)

题解:

首先将一张图 \(G\),若 \(a_{i,j}=1\)\(i,j\) 之间有边,思考一种映射方式将这个图映射到一棵树。
考虑当前的树根 \(x\) 要满足的条件是 \(x\) 跟其连通块中的所有点都有连边,方案数就乘上符合这个条件的点的个数。
如果有多个符合条件的点可以任选一个当根,连通性不变,然后递归到新产生的所有连通块中处理。

代码
#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=405,mod=998244353;
int n,a[N][N];
ll ans=0;
bool vis[N];

vector<int> bfs(int s){
	vector<int>vec;
	queue<int>q;
	q.push(s);vis[s]=1;
	vec.push_back(s);
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int v=1;v<=n;v++)
			if(a[x][v]&&!vis[v]){
				vis[v]=1;
				q.push(v);
				vec.push_back(v);
			}
	}
	return vec;
}

void dfs(vector<int>vec){
	if(vec.size()==1) return ;
	int cnt=0,id=0;
	for(int x:vec){
		bool fl=1;
		for(int y:vec) fl&=a[x][y];
		if(fl) cnt++,id=x;
	}
	(ans*=cnt)%=mod;
	if(!cnt) return ;
	for(int i=1;i<=n;i++) a[id][i]=a[i][id]=0;
	for(int x:vec) vis[x]=0;
	vector<vector<int> >tmp;
	for(int x:vec)
		if(x!=id&&!vis[x]) tmp.push_back(bfs(x));
	for(auto x:tmp) dfs(x);
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) a[i][j]=read();
	for(int i=1;i<=n;i++) vis[i]=0;
	vector<int>vec=bfs(1);
	if(vec.size()!=n){
		puts("0");
		return;
	}
	for(int i=1;i<=n;i++)
		if(!a[i][1]){
			puts("0");
			return ;
		}
	ans=1;
	for(int i=1;i<=n;i++) a[i][1]=a[1][i]=0;
	for(int i=1;i<=n;i++) vis[i]=0;
	vector<vector<int> >tmp;
	for(int i=2;i<=n;i++) if(!vis[i]) tmp.push_back(bfs(i));
	for(auto x:tmp) dfs(x);
	printf("%lld\n",ans);
}

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

ARC197E Four Square Tiles

题面:

有一个 \(n\times m\) 的网格,要在网格上放 \(4\)\(k\times k\) 的矩形,任意两个矩形不能有交,问方案数。\(n,m,k\leq 10^9,n,m\leq 3k-1\)

题解:

根据题目限制,没有一行或一列可以放下三个矩形,所以四个矩形一定是左上右上左下右下,设他们的左上角坐标为 \((x_i,y_i)\)
考虑行或列相邻的两个不交,有不等式:

  • \(1\leq x_1,x_1+k\leq x_3,x_3+k-1\leq n\)
  • \(1\leq x_2,x_2+k\leq x_4,x_4+k-1\leq n\)
  • \(1\leq y_1,y_1+k\leq y_3,y_3+k-1\leq n\)
  • \(1\leq y_2,y_2+k\leq y_4,y_4+k-1\leq n\)

化简得:

  • \(1\leq x_1<x_3-k+1\leq n-2k+2\)
  • \(1\leq x_2<x_4-k+1\leq n-2k+2\)
  • \(1\leq y_1<y_3-k+1\leq n-2k+2\)
  • \(1\leq y_2<y_4-k+1\leq n-2k+2\)

方案数是 \(\binom{n-2k+2}{2}^2\binom{m-2k+2}{2}^2\)
容斥掉不合法的情况就是对角的两个矩阵有交,显然不可能两对同时有交,所以只需要算一对有交的方案,另一对方案数是一样的。
那么有交的条件就是:

  • \(1\leq x_1,x_1+k\leq x_3,x_3+k-1\leq n\)
  • \(1\leq x_2,x_2+k\leq x_4,x_4+k-1\leq n\)
  • \(1\leq y_1,y_1+k\leq y_3,y_3+k-1\leq n\)
  • \(1\leq y_2,y_2+k\leq y_4,y_4+k-1\leq n\)
  • \(x_4<x_1+k,y_4<y_1+k\)

化简得:

  • \(1\leq x_2 < x_4-k+1<x_1+1<x_3-k+2\leq n-2k+3\)
  • \(1\leq y_2 < y_4-k+1<y_1+1<y_3-k+2\leq m-2k+3\)

方案数是 \(\binom{n-2k+3}{4}\binom{m-2k+3}{4}\)
所以最终答案为 \(\binom{n-2k+2}{2}^2\binom{m-2k+2}{2}^2-2\binom{n-2k+3}{4}\binom{m-2k+3}{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 mod=998244353;
ll n,m,K;

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 C(ll n,ll m){
	if(n<m||n<0||m<0) return 0;
	ll res=1;
	for(int i=n-m+1;i<=n;i++) (res*=i)%=mod;
	for(int i=1;i<=m;i++) (res*=ksm(i,mod-2))%=mod;
	return res;
}

ll Pow(ll x){
	return x*x%mod;
}

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

void solve(){
	K=read();n=read();m=read();
	if(n<K<<1||m<K<<1) return puts("0"),void();
	printf("%lld\n ",Mod(Pow(C(n-K*2+2,2)*C(m-K*2+2,2)%mod)+mod-Mod(C(n-K*2+3,4)*C(m-K*2+3,4)%mod*2)));
}

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

ARC213B Hamming Distance is not 1

题面:

给定 \(L,R\) 求一个最大的集合 \(S\) 满足其中的元素都在 \([L,R]\) 之间,且任意两个元素的二进制至少差两位。\(0\leq L\leq R\leq 10^18\)

题解:

考虑一个构造是发现所有 \(popcount\) 奇偶性相同的两两之间至少差两位,所以有一个答案是把所有 \(popcount\) 是奇数/偶数的所有数构成集合 \(S\),这样个数是 \(\lfloor\frac n 2\rfloor\)

显然答案不可能超过 \(\lceil\frac n 2\rceil\),很多情况下上面的做法就最优了,但有一种特殊的是 \(L\) 是奇数 \(R\) 是偶数且最高位不同时,可以最高位是 \(0\) 的取 \(L\) 的奇偶性,最高位是 \(1\) 的取 \(R\) 的奇偶性。

代码
#include<bits/stdc++.h>
#define ll long long
#define int 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;
}

ll op,L,R,id;
bool vis[5000005];

ll poc(ll x){
	return __builtin_popcountll(x);
}

void solve(){
	op=read();L=read();R=read();
	id=__lg(L^R);
	if(op==0){
		if(L==R) puts("1");
		else if((L&1)&&!(R&1)){
			if((poc(L)&1)==(poc(R)&1)) printf("%lld\n",(R-L+1)/2+1);
			else if(L+(1ll<<id)>R) printf("%lld\n",(R-L+1)/2+1);
			else printf("%lld\n",(R-L+1)/2); 
		}
		else printf("%lld\n",(R-L+2)/2);
	}
	else{
		if(L==R) vis[0]=1;
		else if((L&1)&&!(R&1)&&(poc(L)&1)!=(poc(R)&1)&&L+(1ll<<id)>R){
			for(int i=L;i<=R;i++){
				if(!(i>>id&1)) vis[i-L]=(poc(i)&1)==(poc(L)&1);
				else vis[i-L]=(poc(i)&1)==(poc(R)&1);
			}
		}
		else{
			for(int i=0;i<=R-L;i++) vis[i]=0;
			int c[2]={0,0};
			for(int i=L;i<=R;i++) c[poc(i)&1]++;
			if(c[1]>c[0]){for(int i=L;i<=R;i++) if(poc(i)&1) vis[i-L]=1;}
			else {for(int i=L;i<=R;i++) if(!(poc(i)&1)) vis[i-L]=1;}
		}
		for(int i=0;i<=R-L;i++) putchar(vis[i]+'0');
		putchar('\n');
	}
}

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

ARC213C Double X

题面:

给你两棵 \(n\) 个点的树和一个数组 \(a_i\)。对于每个 \(1\leq k\leq n\) 求是否存在满足条件的四元组 \((x_1,x_2,x_3,x_4)\),如果存在求 \(\sum_{i=1}^4 a_{x_i}\) 的最小值。

  • 四元组是四个不同于 \(k\) 的两两不同的数。
  • 任意两点在任意一颗树上的路径都经过 \(k\)

\(n\leq 10^5\)

题解:

先考虑不计复杂度的暴力,对于一个 \(k\),把它在第一课树中的所有子树染上不同的颜色,现在就是求 \(4\) 个点在第二课树种属于不同子树且颜色不同。

对于第一棵树 DSU on Tree 来染色,对于第二棵树的每个子树显然只有价值前四小的是有用的,可以线段树 dfs 序维护更改一个点的颜色。对于当前的 \(k\) 线段树询问所有子树有用的点,将颜色和子树连边求二分图最小权匹配。

由于每次点边个数是 \(O(deg_k)\) 级别的,总共就是 \(O(n)\) 的,注意费用流不能 SPFA。总复杂度 \(O(n\log^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;
}

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

const int N=1e5+5,inf=1e9+7;
int n,a[N],dfn[N],low[N],idc[N],col[N],tim,id[N],son[N],fat[N],siz[N];
vector<int>e[N];
ll ans[N];

namespace MCF{
	const int M=N<<1;
	const ll inf=1e18+7;
	int S,T,cnt=1,head[M],pre[M],lst[M];
	bool vis[M];
	ll dis[M],h[M];
	vector<int>A,B,vec;
	struct edge{
		int v,nxt,c,w;
	}e[N*12];
	
	void add(int u,int v,int w){
		e[++cnt]={v,head[u],1,w};
		head[u]=cnt;
		e[++cnt]={u,head[v],0,-w};
		head[v]=cnt;
	}
	
	void Add(int u,int v,int w){
		add(u,v+n,w);
		A.push_back(u);
		B.push_back(v+n);
	}
	
	bool dijkstra(){
		for(int x:vec) vis[x]=0,dis[x]=inf;
		priority_queue<pair<ll,int>,vector<pair<ll,int> >,greater<pair<ll,int> > >q;
		q.emplace(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;i;i=e[i].nxt){
				v=e[i].v;ll 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.emplace(dis[v],v);
				}
			}
		}
		return dis[T]<=inf/2;
	}
	
	ll solve(){
		S=0;T=n+n+1;
		sort(A.begin(),A.end());
		sort(B.begin(),B.end());
		A.erase(unique(A.begin(),A.end()),A.end());
		B.erase(unique(B.begin(),B.end()),B.end());
		for(int x:A) add(S,x,0);
		for(int x:B) add(x,T,0);
		vec=A;vec.insert(vec.end(),B.begin(),B.end());
		vec.push_back(S);vec.push_back(T);
		ll ans=0;int flow=0;
		while(flow<4&&dijkstra()){
			for(int x:vec) h[x]+=dis[x];
			flow++; ans+=h[T];
			for(int x=T;x!=S;x=pre[x]){
				e[lst[x]].c^=1;
				e[lst[x]^1].c^=1;
			}
		}
		for(int x:vec) head[x]=0;
		for(int x:vec) h[x]=0;
		cnt=1;
		A.clear();B.clear();
		if(flow==4) return ans;
		else return -1;
	}
}

namespace TU{
	int siz[N],tim,dfn[N],low[N],id[N],fat[N];
	vector<int>e[N];
	struct Tree{
		int v[4];
		int& operator[](int i){return v[i];}
	}t[N<<2];
	
	int cmin(int x,int y){return a[x]<a[y]?x:y;}
	int cmax(int x,int y){return a[x]>a[y]?x:y;}
	
	Tree pushup(Tree L,Tree R){
		Tree S=L;
		for(int i=0;i<4;i++)
			if(R[i]){
				bool fl=0;
				for(int j=0;j<4;j++)
					if(col[S[j]]==col[R[i]]){
						S[j]=cmin(S[j],R[i]);
						fl=1;
						break;
					}
				if(fl) continue;
				int mx=0;
				for(int j=0;j<4;j++)
					if(a[S[j]]>a[S[mx]]) mx=j;
				S[mx]=cmin(S[mx],R[i]);
			}
		return S;
	}
	
	void pushup(int s){
		t[s]=pushup(t[s<<1],t[s<<1|1]);
	}
	
	void update(int s,int l,int r,int x){
		if(l==r) return ;
		int mid=l+r>>1;
		if(x<=mid) update(s<<1,l,mid,x);
		else update(s<<1|1,mid+1,r,x);
		pushup(s);
	}
	
	Tree 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 pushup(query(s<<1,l,mid,L,R),query(s<<1|1,mid+1,r,L,R));
	}
	
	void build(int s,int l,int r){
		if(l==r){
			t[s][0]=id[l];
			return ;
		}
		int mid=l+r>>1;
		build(s<<1,l,mid);
		build(s<<1|1,mid+1,r);
		pushup(s);
	}
	
	void add(int u,int v){
		e[u].push_back(v);
		e[v].push_back(u);
	}
	
	void dfs(int x,int fa){
		siz[x]=1;dfn[x]=++tim;id[tim]=x;
		for(int v:e[x]){
			if(v==fa) continue;
			fat[v]=x;
			dfs(v,x);
			siz[x]+=siz[v];
		}
	}
	
	void init(){
		tim=0; dfs(1,0);
		for(int i=1;i<=n;i++) low[i]=dfn[i]+siz[i]-1;
		build(1,1,n);
	}
	
	void solve(int T){
		for(int v:e[T]){
			Tree S;
			if(v==fat[T]){
				if(1<=dfn[T]-1&&low[T]+1<=n)
					S=pushup(query(1,1,n,1,dfn[T]-1),query(1,1,n,low[T]+1,n));
				else if(1<=dfn[T]-1) S=query(1,1,n,1,dfn[T]-1);
				else if(low[T]+1<=n) S=query(1,1,n,low[T]+1,n);
			}
			else S=query(1,1,n,dfn[v],low[v]);
			for(int i=0;i<4;i++)
				if(S[i]) MCF::Add(v,idc[col[S[i]]],a[S[i]]);	
		}
		ans[T]=MCF::solve();
	}
	
	void clear(){
		for(int i=1;i<=n;i++) e[i].clear();
	}
}

void upd(int x,int c){
	col[x]=c;
	TU::update(1,1,n,TU::dfn[x]);
}

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

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

void dp(int x,int fa){
	if(!son[x]){
		ans[x]=-1;
		upd(x,x);
		return ;
	}
	for(int v:e[x])
		if(v!=son[x]&&v!=fa){
			dp(v,x);
			for(int i=dfn[v];i<=low[v];i++) upd(id[i],0);
		}
	dp(son[x],x);
	for(int v:e[x])
		if(v!=son[x]&&v!=fa)
			for(int i=dfn[v];i<=low[v];i++) upd(id[i],v);
	for(int v:e[x]) idc[col[v]]=v;
	TU::solve(x);
	upd(x,col[son[x]]);
	for(int v:e[x])
		if(v!=son[x]&&v!=fa)
			for(int i=dfn[v];i<=low[v];i++) upd(id[i],col[x]);
}

void solve(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	a[0]=inf;
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		TU::add(u,v);
	}
	TU::init();
	tim=0; dfs1(1,0);dfs2(1);dp(1,0);
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	putchar('\n');
	for(int i=1;i<=n;i++) idc[i]=col[i]=0;
	for(int i=1;i<=n;i++) e[i].clear();
	TU::clear();
}

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

posted @ 2025-03-14 06:52  programmingysx  阅读(147)  评论(0)    收藏  举报
Title