题目:
OneDay有 n 张钞票,钞票按从 1 到 n 的顺序编号,编号 i的钞票面值为 ai (1≤i≤n)。很不幸,这些钞票中有不少面值为负,OneDay想要使拥有的面值之和最大,需要丢弃一部分钞票。但丢弃第 i 张钞票时,必须同时丢弃所有编号为 ii 的整数倍的钞票。OneDay可以丢弃任意张钞票,那么能获得的最大面值和是多少呢?
Input
第一行输入一个正整数 nn(1≤n≤500),表示钞票数量。
接下来一行输入 nn 个整数 a1,a2,⋯,an (|ai|≤10^5),表示每张钞票面值。
Output
输出一个整数表示最大的面值和。
Example
input
Copy
6 1 -5 3 -2 4 5
output
Copy
8
思路:考虑样例: 1 -5 3 -2 4 5
1 2 3 4 5 6
如果不选第1个,那么后面都不能选,如果不选第2个,那么第2,4,6个都不能选,换句话说,如果我想选第4个,那么第2个肯定要选。倘若我们从第4个到第2个连一条边的话,如果我们选了第4个,那么它指向的第2个也就必须要选。这样题目就回到了最大闭权子图的求法。
定义
有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。
如下图:
![æåå¾](https://i-blog.csdnimg.cn/blog_migrate/62854f3f1033e9dc151ab4a9a561a87e.png)
能选的子图有Ø,{4},{3,4},{2,4},{1,2,3,4},它们的权值分别为0,-1,5,-6,4.
所以最大权闭合子图为{3,4},权值为5.
解法
这个问题可以转化为最小割问题,用网络流解决。
从源点s向每个正权点连一条容量为权值的边,每个负权点向汇点t连一条容量为权值的绝对值的边,有向图原来的边容量全部为无限大。
求它的最小割,割掉后,与源点s连通的点构成最大权闭合子图,权值为(正权值之和-最小割)。
如何理解
割掉一条边的含义
由于原图的边都是无穷大,那么割边一定是与源点s或汇点t相连的。
割掉s与i的边,表示不选择i点作为子图的点;
割掉i与t的边,表示选择i点为子图的点。
如果s与i有边,表示i存在子图中;
如果i与t有边,表示i不存在于子图中。
合法性
只有s与t不连通时,才能得到闭合子图。
如果s与t连通,则存在点i,j,使得s到i有边,i到j连通,j到t有边,所以j一定是i的后继,但选择了i,没有选择j,不是闭合子图。
如果s与t不连通,选择了正权点i,一定选择了i后继中的所有负权点。设j是i的后继中的正权点,则割掉s到j的边是没有意义的,最小割不会割掉它,则j一点被选中,所以i的所有后继都被选中,符合闭合图的定义。
最优性
最小割=(不选的正权之和+要选的负权绝对值之和)
最大权闭合子图=(正权之和-不选的正权之和-要选的负权绝对值之和)=正权值和-最小割
因为正权值和,是定值,而最小割保证值最小,所以最大权闭合子图一定最优。
---------------------
作者:CaptainHarryChen
来源:CSDN
原文:https://blog.csdn.net/can919/article/details/77603353
代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge
{
int from, to, cap, flow;
Edge(int u, int v, int c, int f): from(u), to(v), cap(c), flow(f) {}
};
const int maxn = 505;
struct Dinic
{
int n, m, s, t;
vector<Edge> edges;
vector<int> G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
void init(int n)
{
this->n = n;
for (int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.emplace_back(from, to, cap, 0);
edges.emplace_back(to, from, 0, 0);
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
memset(d, 0, sizeof(d));
queue<int> q;
q.push(s);
d[s] = 0;
vis[s] = 1;
while (!q.empty())
{
int x = q.front();
q.pop();
for (int i = 0; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = 1;
d[e.to] = d[x] + 1;
q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a)
{
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++)
{
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t)
{
this->s = s, this->t = t;
int flow = 0;
while (BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
} ans;
int n;
int main(int argc, char const *argv[])
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d",&n);
ans.init(n+10);
int sum=0;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(x>0) ans.AddEdge(0,i,x);
if(x<0) ans.AddEdge(i,n+1,-x);
if(x>0) sum+=x;
for(int j=i+i;j<=n;j+=i)
{
ans.AddEdge(j,i,INF);
}
}
int res=sum-ans.Maxflow(0,n+1);
cout<<res<<endl;
return 0;
}