题解【P4068 [SDOI2016]数字配对】
传送门
萌新刚学费用瘤,所以本篇题解会尽量讲得详细一些。
前置芝士:
- 最小费用最大流。
- 质因数分解。
设 $cnt_i$ 为 $a_i$ 质因数分解后各项指数之和。
那么 $a_i$ 和 $a_j$ 能匹配的条件为,$a_i\bmod a_j=0$ 且 $cnt_i=cnt_j+1$。
稍微解释一下,若 $a_i$ 是 $a_j$ 的倍数,那么 $a_i$ 肯定包含 $a_j$ 的所有质因数,而又有条件 $cnt_i=cnt_j+1$,说明 $a_i$ 在 $a_j$ 的基础上只多了一个质因数,所以 $\frac{a_i}{a_j}$ 是一个质数。
现在考虑建图,我们根据 $cnt$ 的奇偶性将 $a_i$ 分成两堆儿:$cnt_i$ 为奇数的放在左边,$cnt_i$ 为偶数的放在右边。
考虑到【一个数只能匹配一次】这一条件,我们把流量设成 $b_i$。
新建一个虚拟汇点 $s$,将 $s$ 与左边的每一个点连一条容量为 $b_i$,费用为 $0$ 的边。
新建一个虚拟汇点 $t$,将右边的每一个点与 $t$ 连一条容量为 $b_i$,费用为 $0$ 的边。
通过这样建图,就可以保证每一个 $a_i$ 最多使用 $b_i$ 次。
两边的点就暴力建图,对于两个可以匹配的点 $i,j$,连一条 从 $i$ 到 $j$,容量为 $\infty$,费用为 $c_i\times c_j$ 的边。
中间部分的建图就已经不用考虑 $b_i$ 的限制了,因为两边的连边都已经考虑在内了。所以容量设为无限大。
那么现在就只剩下【价值之和不小于 $0$】 这一条件了。
这就需要对费用流改动一下。
考虑贪心。我们每次用 SPFA 跑最长路,那么每次 SPFA 跑到终点的最长路肯定是下降的。
我们对于当前的这一条增光路,在价值和不小于 $0$ 的情况下尽可能多的添加流量,直到价值和小于 $0$。
坑点:开 long long,边数组别开小。
贴一下巨丑的代码:
#include#include #include #include #include #include #include #include #include using namespace std; typedef long long ll; const int MAXN(210); const ll INF(1e18); int n,a[MAXN],b[MAXN]; ll c[MAXN]; int cnt[MAXN]; int s,t; inline ll Min(ll x,ll y){return x 1) ++res; return res; } struct E{int to,nxt;ll cost,flow;}; E edge[MAXN*MAXN]; int head[MAXN],tot=1; inline void add_edge(int u,int v,ll f,ll w) { edge[++tot].nxt=head[u]; head[u]=tot; edge[tot].to=v; edge[tot].flow=f; edge[tot].cost=w; return; } ll dis[MAXN],flow[MAXN],Cost,Flow; int pre[MAXN],pos[MAXN]; bool inq[MAXN]; queue q; inline bool SPFA() { for(register int i=s;i<=t;i++) dis[i]=-INF,flow[i]=INF,inq[i]=false; q.push(s); dis[s]=0; while(!q.empty()) { int u=q.front(); q.pop(); inq[u]=false; for(register int i=head[u];i;i=edge[i].nxt) { E e=edge[i]; if(dis[e.to] -INF; } inline bool MCMF() { if(!SPFA()) return false; ll now=dis[t]*flow[t]; if(Cost+now>=0) { Cost+=now; Flow+=flow[t]; for(register int u=t;u!=s;u=pre[u]) { int p=pos[u]; edge[p].flow-=flow[t]; edge[p^1].flow+=flow[t]; } return true; } else return Flow+=Cost/(-dis[t]),false; } int main() { scanf("%d",&n); s=0,t=n+1; for(register int i=1;i<=n;i++) scanf("%d",&a[i]); for(register int i=1;i<=n;i++) scanf("%d",&b[i]); for(register int i=1;i<=n;i++) scanf("%lld",&c[i]); for(register int i=1;i<=n;i++) cnt[i]=fenjie(a[i]); for(register int i=1;i<=n;i++) if(cnt[i]&1) add_edge(s,i,b[i],0),add_edge(i,s,0,0); else add_edge(i,t,b[i],0),add_edge(t,i,0,0); for(register int i=1;i<=n;i++) if(cnt[i]&1) for(register int j=1;j<=n;j++) if((a[i]%a[j]==0&&cnt[i]==cnt[j]+1)||(a[j]%a[i]==0&&cnt[j]==cnt[i]+1)) add_edge(i,j,INF,c[i]*c[j]),add_edge(j,i,0,-c[i]*c[j]); while(MCMF()); printf("%lld\n",Flow); return 0; }
$$\texttt{The End.by UF}$$