AT abc317&318 F&G

ABC317F

给定四个正整数 N,A1,A2,A3,试求满足一下条件的三元组 (X1,X2,X3) 的个数,对 998244353 取模。

  • 1XiN,i=1,2,3
  • AiXii=1,2,3
  • X1X2X3=0

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

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

然后暴力跑记忆化搜索即可。当前二进制位所填 i,j,k 需要满足 ijk=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 的点组成的集合,PS 的子集,则若 P,|F(P)||P|,则左部点都可以被匹配上,此时存在左部的完备匹配。

正则二分图:对于一张二分图 GvG,degv=k,其中 k 为定值,则我们称 Gk 正则二分图。

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

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

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

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

它的流程是这样的:

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

下面我们看正题。

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

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

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

N,M100

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

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

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

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

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

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

所以可以直接暴力跑 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 个手臂长度为 Li。同时有 N 个宝藏,第 i 个宝藏的坐标是 Xi

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

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

  • 1  N 200
  • $ -10{18} \leq X_1 < X_2 < \cdots < X_N\leq 10 $
  • 1 L1 L2 LN 1018
  • 输入都是整数。

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

bi=|aik|,将 b 自大到小排序

  • i[1,n],biLi

这实际上是一个配对,若将点 i 与第 j 个手臂配对,则须有 xiljkxi+lj。记这个不等式为 (i,j)

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

抽出不等式的两个端点,将右端点加一,总计为 d1d2n2,将其自小到大排序。那么新的两个相邻端点 [di,di+1)之间的点都是等效的。

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

#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

给定一张无向连通图,问是否存在一条路径,使得 BAC 的路径上。

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

否则无解。

圆方树的建立:对于每一个点双连通分量,编号 n+1n+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 @   spdarkle  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示