【2019雅禮集訓】【最大費用流】【模型轉換】D2T3 sum

T_Y_P_E發表於2019-01-09

題意

現在你有一個集合{1,2,3,…,n},要求你從中取出一些元素,使得這些元素兩兩互質。問你能夠取出的元素總和最多是多少?

輸入格式

一個整數n

輸出格式

一個整數表示能夠選出的最大的元素總和。

思路

這道題居然是結論+網路流?根本想不到啊…~~
感覺寫題解都只能照搬官方題解了~~

首先出題人就給了兩個自己也沒有證出來的結論:

  1. 最後選出的數至多隻有兩個不同的質因子
  2. 每個選出的數val所包含的質因子如果有兩個的話,一定是一個小於(sqrt{val}),還有一個是大於(sqrt{val})

至於為什麼呢?好像沒有人知道…
然後我們考慮建圖。如果暴力建圖(就是像二分圖那樣),將所有的質因子分為兩個部,左邊是小於等於(sqrt {n})的,右邊是大於(sqrt {n}),然後兩個部之間兩兩連邊就可以了。但是這樣子邊的數量好像多了一點…”據說”可能會T(沒試過)。

那麼就是優化。
首先,先定義F(pri[1],pri[2]…pri[k])為包含pri[]這些質因子的最大的小於等於n的數。然後我們能夠發現,F(pri[1]),F(pri[2]),F(pri[3])…F(pri[pri.size()-1])這些數字是比較優秀的。
然後由於分成了兩個部,我們考慮左邊一個數質數x,右邊一個數質數y,如果我們加入F(x,y)的話,我們能夠發現F(x)和F(y)就不能選了。那麼就會獲得(F(x,y)-F(x)-F(y))的貢獻。只有當這個值大於0的時候,我們才將這條邊加進去。
巨集觀感受下,這樣子之後邊的數量就變得很少了…然後你可以過了…

建圖的話,就是源點S連向左邊的部,容量為1,費用為0;
左邊的部按照上述方式向右邊的部連邊,容量為1,費用為(F(x,y)-F(x)-F(y))
右邊的部向匯點T連邊,容量為1,費用為0。
然後跑一個最大費用流(注意不是最大流!!wa了好久!!注意噹噹前求出來的費用小於0時需要退出!!否則答案就會偏小),在加上最開始的初始值((sum_{i=1}^{k}F(pri[i]))),就是最後的答案了。

程式碼

我的實現是建立負權邊跑最小費用流,可能具體細節和上述的做法是反著的。如果你採用SPFA求最長路的話,細節和上面就是一樣的了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define MAXN 200000
#define INF 1000000000000000000LL
using namespace std;
typedef long long LL;
struct node
{
    int to;
    LL cap,cost;
    node *nxt,*bck;
}edges[MAXN*10+5];
node *ncnt=&edges[0],*Adj[MAXN+5];
int S,T,n;
vector<LL> prime,p1,p2;
bool vis[MAXN+5],inque[MAXN+5];
LL maxval[MAXN+5],dist[MAXN+5],d[MAXN+5];
int prevv[MAXN+5];
node *preve[MAXN+5];
queue<int> que;
void Sieve()
{
    for(int i=2;i<=n;i++)
    {
        if(vis[i]==false)
            prime.push_back(i);
        for(int j=0;1LL*prime[j]*i<=n;j++)
        {
            vis[i*prime[j]]=true;
            if(i%prime[j]==0)
                break;
        }
    }
}
LL F(LL x,LL y)
{
    LL ret=y;//由於y>sqrt(n),所以y這個質因子在ret中只會出現一次
    while(1LL*ret*x<=n)
        ret*=x;
    return ret;
}
void AddEdge(int u,int v,LL cap,LL cost)
{
    node *p=++ncnt;
    p->to=v;p->cap=cap;p->cost=cost;
    p->nxt=Adj[u];Adj[u]=p;
    
    node *q=++ncnt;
    q->to=u;q->cap=0;q->cost=-cost;
    q->nxt=Adj[v];Adj[v]=q;
    
    p->bck=q;q->bck=p;
}
void Build()
{
    S=0,T=(int)prime.size()+1;
    for(int i=0;i<(int)prime.size();i++)
        if(1LL*prime[i]*prime[i]<=n)
            AddEdge(S,i+1,1,0),p1.push_back(i);
        else
            AddEdge(i+1,T,1,0),p2.push_back(i);
    for(int i=0;i<(int)p1.size();i++)
        for(int j=0;j<(int)p2.size();j++)
        {
            LL num1=prime[p1[i]],num2=prime[p2[j]];
            LL num=F(num1,num2);
            if(num-maxval[p1[i]]-maxval[p2[j]]>0)
            {
//              printf("val:%lld
",num-maxval[p1[i]]-maxval[p2[j]]);
                AddEdge(p1[i]+1,p2[j]+1,1,-num+maxval[p1[i]]+maxval[p2[j]]);
            }
        }
}
void SPFA()
{
    while(que.empty()==false)
        que.pop();
    memset(inque,0,sizeof(inque));
    fill(dist,dist+T+1,INF);
    fill(d,d+T+1,INF);
    dist[S]=0;
    que.push(S);inque[S]=true;
    while(que.empty()==false)
    {
        int u=que.front();
        que.pop();inque[u]=false;
        for(node *p=Adj[u];p!=NULL;p=p->nxt)
        {
            int v=p->to;
            LL w=p->cost;
            if(p->cap&&dist[v]>dist[u]+w)
            {
                if(inque[v]==false)
                    inque[v]=true,que.push(v);
                d[v]=min(d[u],p->cap);
                dist[v]=dist[u]+w;
                prevv[v]=u;preve[v]=p;
            }
        }
    }
}
LL Min_Cost_Flow()
{
    LL flow=0,cost=0,delta;
    int u,v;
    while(true)
    {
        SPFA();
        if(d[T]==INF||dist[T]>=0)//就是這裡,一定要判一下!!!否則答案偏小
            break;
        delta=d[T];
        for(v=T;v!=S;v=u)
        {
            u=prevv[v];
            node *&p=preve[v];
            p->cap-=delta;
            p->bck->cap+=delta;
            cost+=1LL*p->cost*delta;
        }
    }
    return cost;
}
int main()
{
    scanf("%d",&n);
    Sieve();
    LL ans=1LL;
    for(int i=0;i<(int)prime.size();i++)
        maxval[i]=F(prime[i],1LL),ans+=maxval[i];//先確定初始的值
//  printf("%lld
",ans);
    Build();
    ans-=min(0LL,Min_Cost_Flow());//加上可能存在的更有的方案(應該可以不用取min,懶得改了...)
    printf("%lld
",ans);
    return 0;
}

相關文章