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即可。