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\) 次最大流的做法,明天补补。