A - CF528C Data Center Drama
Sol
加完边之后存在合法的定向方案的充分必要条件是:所有点的度数都是偶数并且边的总数是偶数。
必要性显然。而如果条件满足,则原图一定存在欧拉回路且欧拉回路的长度是偶数,把欧拉回路求出来然后令第 1 , 3 , 5 ⋯ 1,3,5\cdots 1,3,5⋯条边的方向与遍历它的方向相同,第 2 , 4 , 6 ⋯ 2,4,6\cdots 2,4,6⋯条边方向与遍历它的方向相反,就可以得到满足题意的定向方案。
所以加边的方案一定是:首先把度数为奇数的点两两配对连边,最后如果边数是偶数再连一条自环。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e5+10,M=3e5+10;
vector<PII> G[N];
vector<int> E,P;
int n,m,du[N];
int cur[N],vis[M];
void dfs(int u) {
for(int &i=cur[u];i<G[u].size();++i) if(!vis[G[u][i].sec]) {
int v=G[u][i].fir;
vis[G[u][i].sec]=1;
dfs(v);
P.PB(v);
}
}
int main() {
rd(n),rd(m);
for(int i=1,x,y;i<=m;++i) {
rd(x),rd(y);
G[x].PB(MP(y,i));
G[y].PB(MP(x,i));
du[x]^=1,du[y]^=1;
}
for(int i=1,lst=0;i<=n;++i) if(du[i]) {
if(lst) {
++m;
G[lst].PB(MP(i,m));
G[i].PB(MP(lst,m));
lst=0;
}
else lst=i;
}
if(m&1) {
m++;
G[1].PB(MP(1,m));
}
printf("%d\n",m);
dfs(1);
for(int i=0;i+1<P.size();++i) {
if(i&1) printf("%d %d\n",P[i],P[i+1]);
else printf("%d %d\n",P[i+1],P[i]);
}
printf("%d %d\n",P.back(),P[0]);
return 0;
}
–
B - AGC038F Two Permutations
Sol
将 P P P看成每个点出度为 1 1 1的有向图,则题目条件等价于可以将一个环中的点 { x 1 , x 2 ⋯ x m } \{x_1,x_2\cdots x_m\} {x1,x2⋯xm}的 A i A_i Ai确定为 { x 1 , x 2 ⋯ x m } \{ x_1,x_2\cdots x_m\} {x1,x2⋯xm}或者 { P x 1 , P x 2 , ⋯ P x m } \{P_{x_1},P_{x_2},\cdots P_{x_m}\} {Px1,Px2,⋯Pxm}。 Q , B Q,B Q,B同理,用 a i , b i = 0 / 1 a_i,b_i=0/1 ai,bi=0/1表示 A i A_i Ai被确定为 i / P i i/P_i i/Pi, B i B_i Bi被确定为 i / Q i i/Q_i i/Qi。
- 如果 P i = i , Q i = i P_i=i,Q_i=i Pi=i,Qi=i,则第 i i i位置无论如何都不会产生贡献。
- 如果 P i = i , Q i ≠ i P_i=i,Q_i\not =i Pi=i,Qi=i,则只要 b i = 1 b_i=1 bi=1就一定会产生贡献。对于 P i ≠ i , Q i = i P_i\not =i,Q_i = i Pi=i,Qi=i同理。
- 如果 P i ≠ Q i , P i ≠ i , Q i ≠ i P_i\not=Q_i,P_i\not=i,Q_i\not=i Pi=Qi,Pi=i,Qi=i,那么只有当 a i = b i = 0 a_i=b_i=0 ai=bi=0的时候不会产生贡献。
- 如果 P i = Q i , P i ≠ i P_i=Q_i,P_i\not= i Pi=Qi,Pi=i,那么只有 a i ≠ b i a_i\not =b_i ai=bi的时候会产生贡献。
这可以转化成最小割模型。建一排点分别表示 P P P和 Q Q Q的所有环。 P P P中的环与 S S S之间的边表示 a i = 0 a_i=0 ai=0,与 T T T之间的边表示 a i = 1 a_i=1 ai=1; Q Q Q中的环与 S S S之间的边表示 b i = 1 b_i=1 bi=1,与 T T T之间的边表示 b i = 0 b_i=0 bi=0。对于3.,连从 P P P中的环到 Q Q Q中的环的边;对于4.,在那两个环之间连双向边。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2e5+10;
int S,T,ncnt;
namespace Flow {
int head[N],cur[N],dep[N],ecnt;
struct ed { int to,next,f; };
vector<ed> e;
void init() { memset(head,-1,sizeof(head)); e.clear(); }
void ad(int x,int y,int f) {
e.PB((ed){y,head[x],f}); head[x]=e.size()-1;
e.PB((ed){x,head[y],0}); head[y]=e.size()-1;
}
queue<int> que;
bool bfs() {
for(int i=1;i<=ncnt;++i) dep[i]=-1,cur[i]=head[i];
while(!que.empty()) que.pop();
que.push(S),dep[S]=0;
while(!que.empty()) {
int u=que.front(); que.pop();
if(u==T) return 1;
for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(dep[v]==-1) {
dep[v]=dep[u]+1;
que.push(v);
}
}
}
return 0;
}
int dfs(int u,int f) {
if(u==T||!f) return f; int tmp,ret=0;
for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(e[k].f,f)))) {
e[k].f-=tmp,e[k^1].f+=tmp;
f-=tmp,ret+=tmp;
if(!f) break;
}
}
return ret;
}
int work() { int ans=0; while(bfs()) ans+=dfs(S,1e9); return ans; }
}
int P[N],Q[N],n;
int bP[N],bQ[N],cntP,cntQ;
int id[N][2];
int a[N],b[N],ans;
void dfsP(int u) { if(bP[u]) return; bP[u]=cntP,dfsP(P[u]); }
void dfsQ(int u) { if(bQ[u]) return; bQ[u]=cntQ,dfsQ(Q[u]); }
int main() {
Flow::init();
rd(n);
for(int i=0;i<n;++i) rd(P[i]);
for(int i=0;i<n;++i) rd(Q[i]);
for(int i=0;i<n;++i) if(!bP[i]) cntP++,dfsP(i);
for(int i=0;i<n;++i) if(!bQ[i]) cntQ++,dfsQ(i);
S=++ncnt,T=++ncnt;
for(int i=1;i<=cntP;++i) id[i][0]=++ncnt;
for(int i=1;i<=cntQ;++i) id[i][1]=++ncnt;
for(int i=0;i<n;++i) {
if(P[i]==i&&Q[i]==i) continue;
if(P[i]==i) b[bQ[i]]++;
else if(Q[i]==i) a[bP[i]]++;
else if(Q[i]!=P[i]) ans++,Flow::ad(id[bP[i]][0],id[bQ[i]][1],1);
else ans++,Flow::ad(id[bP[i]][0],id[bQ[i]][1],1),Flow::ad(id[bQ[i]][1],id[bP[i]][0],1);
}
for(int i=1;i<=cntP;++i) {
ans+=a[i];
Flow::ad(S,id[i][0],0);
Flow::ad(id[i][0],T,a[i]);
}
for(int i=1;i<=cntQ;++i) {
ans+=b[i];
Flow::ad(S,id[i][1],b[i]);
Flow::ad(id[i][1],T,0);
}
ans-=Flow::work();
printf("%d",ans);
return 0;
}
C - AGC029E Wandering TKHS
Sol
考虑 u u u到 1 1 1的路径上(不包括 u u u)编号最大的那个点 x x x,显然从 u u u走到 x x x之后,需要经过的点就不再需要考虑 x x x向 u u u走的这棵子树内的点了,这可以直接由 x x x的答案减去 u u u所在的子树对 x x x的答案的贡献得到。
而从 u u u走到 x x x需要的代价是,当我们只认为 i d < x id< x id<x的点和 u u u点存在的时候, u u u所在的连通块的大小-1。并查集维护即可。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2e5+10;
namespace DSU {
int fa[N],sz[N];
void init(int n) { for(int i=1;i<=n;++i) fa[i]=i,sz[i]=1; }
int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); }
int siz(int x) { return sz[find(x)]; }
void un(int x,int y) {
int fx=find(x),fy=find(y);
if(fx==fy) return;
sz[fy]+=sz[fx],fa[fx]=fy;
}
}
vector<int> G[N],Q[N];
int fa[N],son[N],sf[N],n;
int mx[N],ans[N],sub[N];
void dfs1(int u,int last) {
fa[u]=last,mx[u]=max(mx[last],last);
sf[u]=son[mx[u]];
for(int i=0;i<G[u].size();++i) {
int v=G[u][i]; if(v==last) continue;
son[u]=v,dfs1(v,u);
}
}
void dfs2(int u,int last) {
if(last) ans[u]+=ans[mx[u]]-sub[son[mx[u]]]+1;
for(int i=0;i<G[u].size();++i) {
int v=G[u][i]; if(v==last) continue;
son[u]=v,dfs2(v,u);
}
}
int main() {
rd(n); DSU::init(n);
for(int i=1,x,y;i<n;++i) rd(x),rd(y),G[x].PB(y),G[y].PB(x);
dfs1(1,0);
for(int i=2;i<=n;++i) Q[mx[i]].PB(i);
for(int i=1;i<=n;++i) {
for(int j=0;j<Q[i].size();++j) {
int u=Q[i][j];
if(u<i) ans[u]=DSU::siz(u)-1;
else {
if(sf[u]!=u) ans[u]+=DSU::siz(sf[u]);
for(int k=0;k<G[u].size();++k)
if(G[u][k]!=fa[u]&&G[u][k]<i) {
sub[G[u][k]]=DSU::siz(G[u][k]);
ans[u]+=sub[G[u][k]];
}
}
}
for(int j=0;j<G[i].size();++j)
if(G[i][j]<i) DSU::un(i,G[i][j]);
}
dfs2(1,0);
for(int i=2;i<=n;++i) printf("%d ",ans[i]);
return 0;
}
++k)
if(G[u][k]!=fa[u]&&G[u][k]<i) {
sub[G[u][k]]=DSU::siz(G[u][k]);
ans[u]+=sub[G[u][k]];
}
}
}
for(int j=0;j<G[i].size();++j)
if(G[i][j]<i) DSU::un(i,G[i][j]);
}
dfs2(1,0);
for(int i=2;i<=n;++i) printf("%d ",ans[i]);
return 0;
}