ABC263G 解题报告

题意:黑板上有一些数字,其中有 \(n\) 种不同的数字,第 \(i\) 种数字是 \(a_i\),有 \(b_i\) 个。每次可以选择擦掉黑板上的两个数字,需要这两个数字加起来是质数。求最多能擦掉多少个数字。

\(1 \le n \le 100, 1 \le a_i \le 10^7, 1 \le b_i \le 10^9\)

分析:质数,只有 \(2\) 是偶数,其他都是奇数。很容易发现除了 \(1\),如果把能一起擦掉的数字连边,会构成一个二分图。这样的话直接跑最大流即可。考虑加上 \(1\) 怎么办。

\(1\):(by GeZhiYuan)

首先想到仍然跑最大流,在之后的残量网络中再将 \(1\) 两两配对。但这样不是最优的。为什么呢?因为我们找最大流的时候可能找到了已经把比较多 \(1\) 配对了,影响了后面让 \(1\) 自己配对。也许存在一个流量和找到的最大流流量相等或者少一点的方案,使得剩下的 \(1\) 更多,让答案更优。

那么我们考虑先加上非 \(1\) 的边,把这个网络先跑一边最大流榨干掉,然后再加 \(1\) 的边,继续利用残量网络中剩余容量再跑一遍最大流。最后再处理剩下来的 \(1\)

这个东西看起来很对,结果也是对的,但是我不能证明出它确实是对的。

时间复杂度 \(O(n^2 m)+O(n \sqrt v)\)\(m \sim O(n^2)\),所以是 \(O(n^4)+O(n \sqrt v)\),这个 \(n^4\) 很松,能过。只跑了 \(26ms\)。(不理解为什么 GeZhiYuan 大佬跑了 \(766ms\)

另外再次提醒贴网络流模板要注意在 main 函数内把 head memset 为 -1

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
const int inf = 1e18;
int n, a[110], b[110];
int s, t, cnt;
int head[10010], nxt[200010], to[200010], c[200010], cur[10010];
int d[10010];

bool is_prime(int x){
    for(int i=2;i*i<=x;i++)if(x%i==0)return 0;
    return 1;
}
void add(int u, int v, int w)
{
    nxt[cnt] = head[u];
    to[cnt] = v;
    c[cnt] = w;
    head[u] = cnt++;
    nxt[cnt] = head[v];
    to[cnt] = u;
    c[cnt] = 0;
    head[v] = cnt++;
}
bool bfs()
{
    queue<int> q;
    q.push(s);
    cur[s] = head[s];
    memset(d, -1, sizeof d);
    d[s] = 0;
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        if (u == t)
            return 1;
        for (int i = head[u]; ~i; i = nxt[i])
        {
            int v = to[i];
            if (d[v] != -1 || !c[i])
                continue;
            d[v] = d[u] + 1;
            cur[v] = head[v];
            q.push(v);
        }
    }
    return 0;
}
int find(int u, int limit)
{
    if (u == t)
        return limit;
    int flow = 0;
    for (int i = cur[u]; ~i && flow < limit; i = nxt[i])
    {
        cur[u] = i;
        int v = to[i];
        if (d[v] == d[u] + 1 && c[i] > 0)
        {
            int f = find(v, min(limit - flow, c[i]));
            if (!f)
                d[v] = -1;
            c[i] -= f;
            c[i ^ 1] += f;
            flow += f;
        }
    }
    return flow;
}
int dinic()
{
    int maxflow = 0, flow; 
    while (bfs())
        while (flow = find(s, inf))
            maxflow += flow;
    return maxflow;
}
int one;int two;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //think twice,code once.
    //think once,debug forever.
    cin >> n;memset(head, -1, sizeof(head));
    f(i,1,n)cin>>a[i]>>b[i];
    s=n+1,t=n+2;
    f(i,1,n){
        if(a[i]==1){one=i;two = cnt;}
        if(a[i]&1)add(s,i,b[i]);
        else add(i,t,b[i]);
    }
    f(i,1,n){
        f(j,i+1,n){
            if(is_prime(a[i]+a[j]) && i != one && j != one){
                if(a[i]&1)add(i,j,1000000000); else add(j,i,1000000000);
            }
        }
    }
    int ans = dinic();
    f(i,1,n){
        if(is_prime(a[i]+1) && i != one){
            add(one,i,1000000000);
        }
    }
    ans += dinic(); 
    if(one) ans += c[two] / 2;
    cout << ans << endl;
    return 0;
}

看题解,有一个三分(这个是新知识,要开一个专题,已经学会了)做法和一个跑 \(3\) 次最大流的做法,明天补补。

posted @ 2022-08-09 00:23  OIer某罗  阅读(16)  评论(0编辑  收藏  举报