AT abc317&318 F&G

ABC317F

给定四个正整数 \(N,A_1,A_2,A_3\),试求满足一下条件的三元组 \(\left(X_1,X_2,X_3 \right)\) 的个数,对 \(998244353\) 取模。

  • \(1 \le X_i \le N,i=1,2,3\)
  • \(A_i \mid X_i\)\(i=1,2,3\)
  • \(X_1 \oplus X_2 \oplus X_3=0\)

超级数位DP。这显然是数位DP

我们暴力地设当前统计到了第几位,三个数分别的最高位限制情况,以及对三个 \(A\) 取模的余数,并且判断一下0的存在性。

然后暴力跑记忆化搜索即可。当前二进制位所填 \(i,j,k\) 需要满足 \(i\oplus j\oplus k=0\)

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int p=998244353;
int n,a1,a2,a3,len,num,a[100],x1,x2,x3;
int f[70][2][2][2][10][10][10][2][2][2];
int dfs(int n,int l1,int l2,int l3,int m1,int m2,int m3,int f1,int f2,int f3){
	if(n==-1){
		if(f1+f2+f3==3&&m1+m2+m3==0){
			return 1;
		}
		return 0;
	}
	if(f[n][l1][l2][l3][m1][m2][m3][f1][f2][f3]!=-1)return f[n][l1][l2][l3][m1][m2][m3][f1][f2][f3];
	int u1=(l1?a[n]:1),u2=(l2?a[n]:1),u3=(l3?a[n]:1),res=0;
	for(int i=0;i<=u1;i++){
		for(int j=0;j<=u2;j++){
			for(int k=0;k<=u3;k++){
				if(i^j^k)continue;
				res=(res+dfs(n-1,l1&&(i==a[n]),l2&&(j==a[n]),l3&&(k==a[n]),(m1+(1ll<<n)*i)%a1,(m2+(1ll<<n)*j)%a2,(m3+(1ll<<n)*k)%a3,f1|i,f2|j,f3|k))%p;	
			}
		}
	}
	return f[n][l1][l2][l3][m1][m2][m3][f1][f2][f3]=res; 
}	
signed main(){
	memset(f,-1,sizeof f);
	cin>>n>>a1>>a2>>a3;
	while(n){
		a[num++]=(n&1);
		n>>=1;
	}
	cout<<dfs(num-1,1,1,1,0,0,0,0,0,0)<<"\n";
	
}

ABC317G

可以看看这个:Hall定理——K正则二分图的完美匹配

内容:给定一张二分图,其中设左部点集合为 \(S\),右部点为 \(T\),记 \(F(P)\)\(P\) 中连向 \(T\) 的点组成的集合,\(P\)\(S\) 的子集,则若 \(\forall P,|F(P)|\ge |P|\),则左部点都可以被匹配上,此时存在左部的完备匹配。

正则二分图:对于一张二分图 \(G\)\(\forall v\in G,deg_v=k\),其中 \(k\) 为定值,则我们称 \(G\)\(k\) 正则二分图。

显然在这里 \(|S|=|T|\),因为边数为 \(k|S|=k|T|\)

容易发现正则二分图满足Hall定理。

故正则二分图必定存在完美匹配,如何求完美匹配呢?

网络流和匈牙利都可以做到,但有一个非常神笔的做法,可以做到 \(O(n\log n)\)

它的流程是这样的:

  • 重复 \(n\) 次.
  • 每次找一个左边的未匹配点,沿着增广路随机游走,直到走到一个右边的未匹配点.
  • 把路径上的环去掉,然后增广。

下面我们看正题。

有一个 \(N\)\(M\) 列的矩阵,其中 \(1\sim N\) 各出现了恰好 \(M\) 次,你可以进行任意次操作,每次操作可以把一行任意重排,最后要使得矩阵每一列都是一个长度为 \(N\) 的排列。

如果不能达到目标,输出 No

如果能达到目标,输出一行 Yes,之后输出重排后的矩阵。

\(N,M\le 100\)

很显然,对于每一列,我们都需要有一个排列。那么我们考虑拿出这个排列。

如何?显然我们也可以通过建图得到。不过重排嘛,如果只需要满足当前列的话,我们显然是直接每行拿出一个数然后组合即可。

将行号抽离出来,作为右部点,然后 \(1\sim n\) 作为左部点。

每一行向这一行出现的数连边,就形成了一个 \(n\) 正则二分图匹配。

我们发现,每一个完美匹配,都对应了一列。等价于要求一个 \(n\) 正则二分图的 \(n\) 个完美匹配。

How?可能现在方案不好取了。但试想,我们删除了一个完美匹配之后,其实每个点的度数都会减少1,则变成了 \(n-1\) 正则二分图,照样存在完美匹配。

所以可以直接暴力跑 \(n\) 次找完美匹配。

本题用最大流算法足以通过。所以本题必定有解,No是骗人的


#include<iostream>
#include<queue>
using namespace std;
#define N 25050
int ans[505][505],num,dep[N],head[N],ver[N],nxt[N],cost[N],tot=1,s,t,n,m,vis[N],cur[N];
void init(){
	cin>>n>>m;
	s=n+n+1,t=n+n+2,num=t;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int x;cin>>x;
			add(i,x+n,1);
		}
	}
	for(int i=1;i<=n;i++)add(s,i,1);
	for(int i=n+1;i<=n+n;i++)add(i,t,1);
}
void solve(int id){
	int res=dinic();
	if(res!=n){
		cout<<"No\n";exit(0);
	}
	for(int i=2;i<=2*n*m;i+=2){
		int u=ver[i^1],v=ver[i]-n;
		if(cost[i^1]){
			vis[i]=vis[i^1]=1;cost[i]=cost[i^1]=0;
			ans[u][id]=v;
		}
	}
	for(int i=2;i<=tot;i+=2){
		if(cost[i]==0&&vis[i]==0){
			cost[i]++,cost[i^1]--;
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	init();
	for(int i=1;i<=m;i++)solve(i);
	cout<<"Yes\n";
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)cout<<ans[i][j]<<" ";
		cout<<"\n";
	}
}

ABC318F

有个机器人,它有 \(N\) 个手臂,第 \(i\) 个手臂长度为 \(L_i\)。同时有 \(N\) 个宝藏,第 \(i\) 个宝藏的坐标是 \(X_i\)

当机器人位于 \(k\) 时,它的第 \(i\) 条手臂可以够到 \([k-L_i,k+L_i]\) 范围内的宝藏。

机器人的每条手臂只能选择一个宝藏。请问总共有多少个整数坐标,能够让机器人在这个坐标能够拿到所有宝藏?

  • $ 1\ \leq\ N\leq\ 200 $
  • $ -10{18} \leq X_1 < X_2 < \cdots < X_N\leq 10 $
  • $ 1\leq\ L_1\leq\ L_2\leq\cdots\leq\ L_N\leq\ 10^{18} $
  • 输入都是整数。

先考虑确定了一个位置 \(k\),如何判断其是否合法?我们显然是贪心地把相同排名的 \(L\) 与距离进行匹配。

\(b_i=|a_i-k|\),将 \(b\) 自大到小排序

  • \(\forall i\in[1,n],b_i\le L_i\)

这实际上是一个配对,若将点 \(i\) 与第 \(j\) 个手臂配对,则须有 \(x_i-l_j\le k\le x_i+l_j\)。记这个不等式为 \((i,j)\)

而对于每一个合法的位置 \(k\),需要选出 \(n\) 个满足条件的二元组 \((s_i,t_i)\),使得 \(s,t\) 各为一个排列。

抽出不等式的两个端点,将右端点加一,总计为 \(d_1\sim d_{2n^2}\),将其自小到大排序。那么新的两个相邻端点 \([d_i,d_{i+1})\)之间的点都是等效的。

此时我们枚举每一对相邻端点,暴力判断即可做到 \(O(n^3\log n)\),足以通过本题。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 1050
int x[N],n,ans,l[N],d[N];
vector<int>b;
signed main(){
	ios::sync_with_stdio(false);
	cin>>n; 
	for(int i=1;i<=n;i++)cin>>x[i];
	for(int i=1;i<=n;i++)cin>>l[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			b.push_back(x[i]+l[j]+1);
			b.push_back(x[i]-l[j]);
		}
	}
	sort(b.begin(),b.end());
	for(int i=0;i<b.size()-1;i++){
		int s=b[i];
		for(int j=1;j<=n;j++)d[j]=abs(x[j]-s);
		sort(d+1,d+n+1);int tag=0;
		for(int j=1;j<=n;j++){
			if(l[j]<d[j])tag=1;
		}
		if(!tag)ans+=b[i+1]-b[i];
	}
	cout<<ans<<"\n";
}

ABC318G

给定一张无向连通图,问是否存在一条路径,使得 \(B\)\(A\)\(C\) 的路径上。

建立圆方树,由于询问只有一次,我们暴力地标记树上 \(A\)\(C\) 的路径。只要 \(B\) 被标记或者其所属点双被标记都说明有解。

否则无解。

圆方树的建立:对于每一个点双连通分量,编号 \(n+1\sim n+cnt\),将点双内的点向所属点双连边,点双属于方点,原图点属于圆点。那么这张新图就会是一棵树。

为什么用圆方树做这个题呢?

因为在点双内,全部可达。

#include<bits/stdc++.h>
using namespace std;
#define N 807000
map<int,int>e[N];
int head[N],ver[N],nxt[N],tot=1,cnt,vis[N],num,dfn[N],low[N],c[N],n,m,top,sta[N],rt;
vector<int>dcc[N];
int sh[N],sv[N],sn[N],dep[N],f[N][25],st=1,cir[N];
vector<int>p[N];
void ad_s(int u,int v){
	sn[++st]=sh[u];sv[sh[u]=st]=v;
}
void add_s(int u,int v){
//	cout<<"add: "<<u<<" "<<v<<"\n";
	ad_s(u,v);ad_s(v,u);
}
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void dfs_s(int u,int fa){
	dep[u]=dep[fa]+1;
	f[u][0]=fa;
	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=sh[u];i;i=sn[i]){
		int v=sv[i];
		if(v==fa)continue;
		dfs_s(v,u);
	}
}
int lca(int u,int v){
	if(dep[u]>dep[v])swap(u,v);
	for(int i=20;i>=0;--i)if(dep[f[v][i]]>=dep[u])v=f[v][i];
	if(u==v)return v;
	for(int i=20;i>=0;--i)if(f[v][i]!=f[u][i])v=f[v][i],u=f[u][i];
	return f[u][0];
}
void dfs(int u,int fa){
	low[u]=dfn[u]=++num;sta[++top]=u;
	if(u==rt&&head[u]==0){
		++cnt;c[u]=cnt;
		dcc[cnt].push_back(u);return ;
	}
	int tag=0,lst=0;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(dfn[v])low[u]=min(low[u],dfn[v]);
		else {
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(dfn[u]<=low[v]){
				int z;++cnt;
				do{
					z=sta[top--];
					c[z]=cnt;add_s(z,cnt);
					dcc[cnt].push_back(z);
				}while(z!=v);
				add_s(u,cnt);
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;int A,B,C;cin>>A>>B>>C;cnt=n;
	for(int i=1;i<=m;i++){
		int u,v;cin>>u>>v;
		add(u,v);add(v,u);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			rt=i;dfs(i,0);
		}
	}
	for(int i=1;i<=cnt;i++)if(!dep[i])dfs_s(i,0);
	int p=A,q=C,s=lca(p,q);
	while(p!=s){
		cir[p]=1;p=f[p][0];
	}
	while(q!=s){
		cir[q]=1;q=f[q][0];
	}
	cir[s]=1;
	for(int i=sh[B];i;i=sn[i]){
		cir[B]|=cir[sv[i]];
	}
	if(cir[B])cout<<"Yes\n";
	else cout<<"No\n";
}

本题还有网洛流做法。

将每个点拆为 \(u,u'\),连容量为1的边。

将源点连接AC,容量为1,B连向汇点,容量为2。

判断最大流是否为2即可。

posted @ 2023-09-03 23:58  spdarkle  阅读(11)  评论(0编辑  收藏  举报