分析:
不久之前写了一道二分图匈牙利
其中提出了一条原则:
正难则反,大变小,流化割
于是在看这道题后立马就上手了
我们可以转换一下角度
求解:删除一些数,使得剩下的数据合法且贡献最大
显然我们删除的数之和应该取最小
这就是一个最小割的模型
建图:
二分图(拆点),每个部都是n个点
源点向X部连边,Y部向汇点连边,容量就是b[i]
两个部之间,如果存在矛盾,就在两点之间连边,容量为INF
那么怎么定义矛盾的产生呢?
题目要求数据满足一下两个条件之一:
(第二个条件可以转化为:两个数不互质)
换句话说:如果两个条件都不符合,那么这两个点就存在矛盾
在判断两个点是否存在矛盾的时候,有以下的双重循环
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
然而这样连完了边之后,需要删除的数我们会计算两遍
因此,最后答案就是:Σbi - dinic(s,t)/2
tip
一开始为了避免重复计算,连边的时候只选择了“编号小连大”,但是这样WA掉了
仔细想了想,应该是因为这种建图方式中,整个图的流量是2*Σbi
因此最小割也应该双倍计算才对(最后除以二就好了)
如果通过“小连大”的方式防止重复计算,为了使整个图不连通,就会多割掉一些边
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#define ll long long
using namespace std;
const int INF=1e9;
const int N=1005;
struct node{
int x,y,nxt,v;
};
node way[N*N*2];
int n,st[N<<1],tot=-1;
int a[N],b[N],s,t,Q[N<<2],cur[N<<1],deep[N<<1];
void add(int u,int w,int z)
{
tot++;
way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
tot++;
way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].nxt=st[w];st[w]=tot;
}
int pd(int a,int b)
{
ll x=(ll)a*a+(ll)b*b;
ll y=sqrt(x);
if (y*y==x) return 1;
else return 0;
}
int gcd(int a,int b)
{
int r=a%b;
while (r)
{
a=b;b=r;
r=a%b;
}
return b;
}
void build()
{
s=0; t=2*n+1;
for (int i=1;i<=n;i++)
{
add(s,i,b[i]);
add(i+n,t,b[i]);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (gcd(a[i],a[j])==1&&pd(a[i],a[j])) add(i,j+n,INF);
}
int bfs(int s,int t)
{
memset(deep,-1,sizeof(deep));
for (int i=s;i<=t;i++) cur[i]=st[i];
int tou=0,wei=0;
Q[++wei]=s;
deep[s]=1;
do
{
int now=Q[++tou];
for (int i=st[now];i!=-1;i=way[i].nxt)
if (way[i].v&&deep[way[i].y]==-1)
{
deep[way[i].y]=deep[now]+1;
Q[++wei]=way[i].y;
}
}
while (tou<wei);
return deep[t]!=-1;
}
int dfs(int now,int t,int limit)
{
if (now==t||!limit) return limit;
int f,flow=0;
for (int i=cur[now];i!=-1;i=way[i].nxt)
{
cur[now]=i;
if (way[i].v&&deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(limit,way[i].v))))
{
flow+=f;
limit-=f;
way[i].v-=f;
way[i^1].v+=f;
if (!limit) break;
}
}
return flow;
}
int dinic()
{
int ans=0;
while (bfs(s,t))
ans+=dfs(s,t,INF);
return ans;
}
int main()
{
memset(st,-1,sizeof(st));
scanf("%d",&n);
int tt=0;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) scanf("%d",&b[i]),tt+=b[i];
build();
printf("%d",tt-dinic()/2);
return 0;
}
在网上找了一下题解,发现dalao都是用的另一种建图方式:
仔细观察两个限制条件:
- 任意两个奇数一定满足第一个条件
- 任意两个偶数一定满足第二个条件
因此奇数之间是不会有连边的,同理偶数之间也是不会有连边的
这恰好符合二分图的性质:同一部分中的点之间没有连边
因此我们可以根据奇偶分成X和Y部,两个部之间再连边
这样点数和边数都比较小,而且不牵扯重复计算的问题,应该会快一点