世界真的很大
网络流并不难,但是其建图方式的多种多样,决定了其能解决的问题的多样性。
而作为网络流的一个变种,在其基础之上加上边权的费用流,除了一眼就能看出的模板题以外,取决于建模方式的灵活变化也很多
而对于这样变化多端的题而言,我们往往很难一眼看出来这道题究竟该不该用网络流或者费用流
我们寻求题解,但却不能仅仅止步于AC,不应该忘记去推导建模的过程
在感叹“哇这道题真是巧”的同时,还是得去想想到底为什么能这么想,或者大佬是怎么把这道题想出来的,“怎么”比“什么”更重要
尽管的却很难
网络流费用流一类正是如此
看题先:
有 n 种数字,第 i 种数字是 ai、有 bi 个,权值是 ci。
若两个数字 ai、aj 满足,ai 是 aj 的倍数,且 ai/aj 是一个质数,
那么这两个数字可以配对,并获得 ci×cj 的价值。
一个数字只能参与一次配对,可以不参与配对。
在获得的价值总和不小于 0 的前提下,求最多进行多少次配对。
第一行一个整数 n。
第二行 n 个整数 a1、a2、……、an。
第三行 n 个整数 b1、b2、……、bn。
第四行 n 个整数 c1、c2、……、cn。
一行一个数,最多进行多少次配对
看题目,问最多的配对次数
先考虑配对条件,理解一下就是说ai等于aj乘上一个质数
由除法,质数什么的可以联想到质因数什么的,就是说ai质因数分解后质因数比aj多一个,而且恰恰多一个
那么对于所有可以匹配的ai,aj,其质因数个数一定是一个奇数一个偶数,不然没法差1对吧
而且我们可以看出,对于质因数个数同为奇数或者偶数的两个数,那肯定是不可能配对的
很明显的可以把图分成一张二分图了,只有在图的两侧的点能互相连接配对
处理二分图的一般就是匈牙利或者网络流了
继续考虑到底是把数分成二分图,还是把数的种类分成二分图
这里就对题目数据范围逆向分析了
种类的范围是200,数的范围是200*10^5就是10^7级别,不管是对于匈牙利还是网络流都太大了一点,相对来说还是200更为合适
那么就按照种类,把a值质因数个数为奇数的排成一列,偶数的排成一列,相互能配对的互相建边
但是如果不按数的个数来的话,b就没有作用了,换句话说,就需要一个方法来限制每种数的数量,很显然,匈牙利貌似没有这个功能
那么就是网络流了,dinic一类的东西
源点想所有奇数点连流量为b值的边,所有偶数点向汇点连流量为b值的边
确立大概算法之后就带着算法的印象来思考
题目要求的是在权值和大小不低于0的情况下考虑最多的匹配次数
考虑本来已经是按照匹配连边的,所以从源点到汇点的一条最大流就是最大流次匹配。
而我们现在要考虑的是最大流的匹配次数带来的权值,就是匹配次数乘以ci*cj
就是最大流*ci*cj
把ci*cj看成权值的话,妥妥的费用流了
费用流有一个找增广路的过程,我们需要的就是找了多少次增广路
那么为了使得增广路尽量多,那么我们就需要使得权值尽量大,这样就离0尽量远
这样就得出:我们找增广路时需要的是权值的最长路而不是最短路
需要特判一下最后跑不完增广路的最大流的情况
费用流就用一般的多遍SPFA的办法
强行模拟增广路的过程,每次都找最长路,这样最后就是最大费用最长路了
单独关于费用流的之后再总结今天就现总结一下解题思路吧
完整代码:
#include
#include
#include
#include
using namespace std;
typedef long long dnt;
const dnt INF=100000000000000000ll ;
struct edge
{
dnt u,v,last,c,w;
}ed[100010];
queue state ;
dnt ans=0,now=0,a[100010],b[100010],c[100010],dis[100010];
dnt n,num=1,ptot=0,S,T;
dnt head[100010],frm[100010],se[100010];
dnt isnot[100010],primes[100010],cnt[100010];
void add(dnt u,dnt v,dnt w,dnt c)
{
num++;
ed[num].u=u;
ed[num].v=v;
ed[num].w=w;
ed[num].c=c;
ed[num].last=head[u];
head[u]=num;
}
void init(dnt N)
{
isnot[1]=1;
for(int i=2;i<=N;i++)
{
if(!isnot[i])
primes[ptot++]=i;
for(int t=0;tif(j>N) break ;
isnot[j]=1;
if(i%primes[t]==0)
break ;
}
}
}
void check(dnt x)
{
dnt tmp=a[x],t=0;
for(int i=0;iif(tmp%primes[i]==0)
{
while(tmp%primes[i]==0)
{
tmp/=primes[i];
cnt[x]++;
}
}
}
}
bool judge(dnt x,dnt y)
{
if(cnt[x]==cnt[y]+1 && a[x]%a[y]==0) return true ;
if(cnt[y]==cnt[x]+1 && a[y]%a[x]==0) return true ;
return false ;
}
bool SPFA()
{
for(dnt i=0;i<=T;i++) dis[i]=-INF;
memset(se,0,sizeof(se));
while(!state.empty()) state.pop();
se[S]=1,dis[S]=0;
state.push(S);
while(!state.empty())
{
int u=state.front();
se[u]=0;
state.pop();
for(int i=head[u];i;i=ed[i].last)
{
dnt v=ed[i].v;
if(ed[i].w && dis[v]if(!se[v])
{
state.push(v);
se[v]=1;
}
}
}
}
if(now+dis[T]<0) return false ;
return dis[T]!=-INF;
}
void MCF()
{
dnt x=INF,i=frm[T];
while(i)
{
x=min(x,ed[i].w);
i=frm[ed[i].u];
}
if(now+dis[T]*x<0)
{
ans+=now/(-dis[T]);
for(dnt i=head[S];i;i=ed[i].last) ed[i].w=0;
return ;
}
i=frm[T];
now+=x*dis[T];
while(i)
{
ed[i].w-=x;
ed[i^1].w+=x;
i=frm[ed[i].u];
}
ans+=x;
}
int main()
{
init(32003);
scanf("%lld",&n);
S=0,T=n+1;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&b[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&c[i]);
for(int i=1;i<=n;i++)
{
check(i);
if(cnt[i]%2)
add(S,i,b[i],0),add(i,S,0,0);
else
add(i,T,b[i],0),add(T,i,0,0);
}
for(int i=1;i<=n;i++)
if(cnt[i]%2)
for(int j=1;j<=n;j++)
if(judge(i,j))
add(i,j,INF,c[i]*c[j]),add(j,i,0,-c[i]*c[j]);
while(SPFA())
MCF();
SPFA();
printf("%lld\n",ans);
return 0;
}
/*
EL PSY CONGROO
*/
嗯,就是这样