【BZOJ3275】Number

探讨了如何使用网络流算法解决一个特定的最大独立集问题,该问题涉及到从一组正整数中选择最大和的子集,但受制于特定的数学条件限制。通过深入分析连边情况和运用Dinic最小割算法,成功地解决了这一问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

题目描述

有N个正整数,需要从中选出一些数,使这些数的和最大。
若两个数a,b同时满足以下条件,则a,b不能同时被选
1:存在正整数C,使a*a+b*b=c*c
2:gcd(a,b)=1

Sol

太菜不会分析+网络流经典模型学习&总结不到位

一开始果断看出这是个最大独立集啊

然后好像不会跑?

分析一下连边情况,首先偶数不会和偶数相连,因为不满足第2个条件

对于第一个式子:

a2+b2=c2 a 2 + b 2 = c 2

a=2k+1,b=2k+1 a = 2 k + 1 , b = 2 k ′ + 1
于是 c=4(k2+k2)+4(k+k)+2 c = 4 ( k 2 + k ′ 2 ) + 4 ( k + k ′ ) + 2 , cmod4=2 c m o d 4 = 2
简单分析发现c不会是完全平方数

所以只有可能奇数和偶数连边,打个表发现 105 10 5 有1万7千多这样的数对

所以就是一个普通的二分图最大独立集了一波Dinic最小割即可

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<cstring>
#define Copy(a,b) memcpy(a,b,sizeof(a))
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
int n;
const int N=3e3+10;
const int INF=3e8+10;
int a[N];
int gcd(int a,int b){return b==0? a:gcd(b,a%b);}
const int MAXN=1e5+10;
typedef long long ll;
inline bool check(int a,int b){
    if(!(a&1)&&!(b&1)) return 0;
    if(gcd(a,b)!=1) return 0;
    ll c=1ll*a*a+1ll*b*b;
    register ll res=sqrt(c);if(res*res==c) return 1;
    return 0;
}
int ans=0;
int num[MAXN];
struct edge{
    int to;int next;int cap;
}A[MAXN];
int head[N];int cnt=0;int cur[N];
void add(int x,int y,int cap){A[cnt]=(edge){y,head[x],cap};head[x]=cnt++;}
int S,T;
int Sum=0;
int d[N];
queue<int> Q;
inline bool bfs(){
    while(!Q.empty()) Q.pop();
    Set(d,-1);d[S]=0;Q.push(S);
    while(!Q.empty()){
        register int u=Q.front();Q.pop();
        for(register int v,i=head[u];~i;i=A[i].next){
            v=A[i].to;
            if(A[i].cap<=0||d[v]!=-1) continue;
            d[v]=d[u]+1;Q.push(v);
            if(v==T) return 1;
        }
    }
    return (d[T]!=-1);
}
int dfs(int u,int flow){
    if(flow==0) return 0;
    if(u==T) return flow;
    int delta=flow;
    for(register int v,&i=cur[u];~i;i=A[i].next){
        v=A[i].to;
        if(A[i].cap<=0||d[v]!=d[u]+1) continue;
        register int fl=dfs(v,min(flow,A[i].cap));
        if(fl==0) d[v]=-1; 
        flow-=fl;A[i].cap-=fl;A[i^1].cap+=fl;
        if(flow==0) break;
    }
    return (delta-flow);
}
inline void Dinic()
{
    register int flow=0;
    while(bfs()) {Copy(cur,head);flow+=dfs(S,INF);}
    printf("%d\n",Sum-flow);
}
int main()
{
    scanf("%d",&n);Set(head,-1);
    for(register int i=1;i<=n;++i) scanf("%d",&a[i]),++num[a[i]],Sum+=a[i];
    sort(a+1,a+1+n);n=unique(a+1,a+1+n)-a-1;S=0;
    for(register int i=1;i<=n;++i)
        for(register int j=i+1;j<=n;++j){
            if(check(a[i],a[j])) {
                if(a[i]&1) add(i,j,INF),add(j,i,0);
                else add(j,i,INF),add(i,j,0);
            }
        }
    T=n+1;
    for(register int i=1;i<=n;++i){
        if(a[i]&1) add(S,i,a[i]*num[a[i]]),add(i,S,0);
        else add(i,T,a[i]*num[a[i]]),add(T,i,0);
    }
    Dinic();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值