01分数规划
POJ 2976 题目链接
题意
n个题目,给出题目的分数(bi)和你的得分(ai),累加平均值的公式为
100
∗
∑
i
=
1
n
a
i
/
∑
i
=
1
n
b
i
100*\sum_{i=1}^{n}{ai} / \sum_{i=1}^{n}{bi}
100∗i=1∑nai/i=1∑nbi
问你去掉k个题目后,最大的累加平均值为多少
思路
错点:用%.0f不能用%d
二分(速度要稍慢于Dinkelbach 110ms),r的取值范围在[0,1]之间
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = 3333;
const int INF = 0x3f3f3f3f;
double a[N],b[N];
double lowcost[N];
double calc(int n,int k,double r)
{
for (int i = 1; i <= n; i++){
lowcost[i] = a[i] - r * b[i];
}
sort(lowcost + 1,lowcost + 1 + n);
double ans = 0;
for (int i = n; i > k; i--){
ans += lowcost[i];
}
return ans;
}
int main()
{
int n,k;
while(scanf("%d %d",&n,&k) == 2 && (n || k)){
for (int i = 1; i <= n; i++){
scanf("%lf",&a[i]);
}
for (int i = 1; i <= n; i++){
scanf("%lf",&b[i]);
}
double l = 0,r = 1;
while(r - l >= eps){
double mid = (l + r) / 2;
if (calc(n,k,mid) > 0){
l = mid + eps;
}
else {
r = mid - eps;
}
}
printf("%.0f\n",(r * 100));
}
return 0;
}
Dinkelbach算法(一般r的初始值为0 47ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = 3333;
const int INF = 0x3f3f3f3f;
struct node{
double a,b,cost;
bool operator < (const node& p) const{
return cost < p.cost;
}
}s[N];
double Dinkelbach(int n,int k,double r)
{
for (int i = 1; i <= n; i++){
s[i].cost = s[i].a - r * s[i].b;
}
sort(s + 1,s + 1 + n);
double cost = 0,sum = 0;
for (int i = n; i > k; i--){
cost += s[i].a;
sum += s[i].b;
}
return cost / sum;
}
int main()
{
int n,k;
while(scanf("%d %d",&n,&k) == 2 && (n || k)){
for (int i = 1; i <= n; i++){
scanf("%lf",&s[i].a);
}
for (int i = 1; i <= n; i++){
scanf("%lf",&s[i].b);
}
double r = 0,l;
while(true){
l = Dinkelbach(n,k,r);
if (fabs(r - l) < eps) break;
r = l;
}
printf("%.0f\n",(r * 100));
}
return 0;
}
POJ 2728 题目链接
题意 最优比例树
输入n条村庄,xy代表坐标,h代表高度,两个坐标的高度差就是花费,求出连通所有点(生成树)的总花费与总长度的最小比例(最优比例树)
思路
最
小
化
∑
i
=
1
n
−
1
c
o
s
t
i
/
∑
i
=
1
n
−
1
l
e
n
i
,
总
共
n
−
1
条
边
;
最小化 \sum_{i=1}^{n-1}{cost_i} / \sum_{i=1}^{n-1}{len_i} ,总共n - 1条边;
最小化i=1∑n−1costi/i=1∑n−1leni,总共n−1条边;
令
∑
i
=
1
n
−
1
l
o
w
i
=
∑
i
=
1
n
−
1
c
o
s
t
i
−
r
∗
∑
i
=
1
n
−
1
l
e
n
i
。
以
l
o
w
i
为
边
,
求
最
小
生
成
树
令 \sum_{i=1}^{n-1}{low_i}=\sum_{i=1}^{n-1}{cost_i} -r* \sum_{i=1}^{n-1}{len_i}。以low_i为边,求最小生成树
令i=1∑n−1lowi=i=1∑n−1costi−r∗i=1∑n−1leni。以lowi为边,求最小生成树
Dinkelbach算法(prim求最小生成树 266ms kruskal是1125ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
double x,y,h;
}p[N];
double len[N][N];
double cost[N][N];
double lowcost[N];
int pre[N];
double Dinkelbach(int n,double r)
{
for (int i = 1; i < n; i++){
lowcost[i] = cost[0][i] - r * len[0][i];
pre[i] = 0;
}
double sum_cost = 0,sum_len = 0;
for (int i = 1; i < n; i++){
double mi = INF;
int pos;
for (int j = 1; j < n; j++){
if (pre[j] != -1 && lowcost[j] < mi){
mi = lowcost[j];
pos = j;
}
}
sum_cost += cost[pos][pre[pos]];
sum_len += len[pos][pre[pos]];
pre[pos] = -1;
for (int j = 1; j < n; j++){
if (pre[j] != -1 && lowcost[j] > cost[pos][j] - r * len[pos][j]){
lowcost[j] = cost[pos][j] - r * len[pos][j];
pre[j] = pos;
}
}
}
return sum_cost / sum_len;
}
double dis(int i,int j)
{
return sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
int main()
{
int n;
while(scanf("%d",&n) == 1 && n){
for (int i = 0; i < n; i++){
scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].h);
}
for (int i = 0; i < n; i++){
for (int j = i + 1; j < n; j++){
len[i][j] = len[j][i] = dis(i,j);
cost[i][j] = cost[j][i] = fabs(p[i].h - p[j].h);
}
}
double r = 0,l;
while(true){
l = Dinkelbach(n,r);
if (fabs(r - l) < eps) break;
r = l;
}
printf("%.3f\n",r);
}
return 0;
}
二分(2454ms)
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
double x,y,h;
}p[N];
double len[N][N];
double cost[N][N];
double lowcost[N];
bool vis[N];
double Binary(int n,double r)
{
for (int i = 1; i < n; i++){
lowcost[i] = cost[0][i] - r * len[0][i];
}
double ans = 0;
memset(vis,false,sizeof(vis));
for (int i = 1; i < n; i++){
double mi = INF;
int pos;
for (int j = 1; j < n; j++){
if (!vis[j] && lowcost[j] < mi){
mi = lowcost[j];
pos = j;
}
}
vis[pos] = true;
ans += mi;
for (int j = 1; j < n; j++){
if (!vis[j] && lowcost[j] > cost[pos][j] - r * len[pos][j]){
lowcost[j] = cost[pos][j] - r * len[pos][j];
}
}
}
return ans;
}
double dis(int i,int j)
{
return sqrt((p[i].x - p[j].x) * (p[i].x - p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
int main()
{
int n;
while(scanf("%d",&n) == 1 && n){
for (int i = 0; i < n; i++){
scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].h);
}
for (int i = 0; i < n; i++){
for (int j = i + 1; j < n; j++){
len[i][j] = len[j][i] = dis(i,j);
cost[i][j] = cost[j][i] = fabs(p[i].h - p[j].h);
}
}
double l = 0,r = 2e9;
while(r - l >= eps){
double mid = (l + r) / 2;
if (Dinkelbach(n,mid) > 0){
l = mid + eps;
}
else r = mid - eps;
}
printf("%.3f\n",r);
}
return 0;
}
POJ 3621 题目链接
题意 最优比例环
给定n个点的权值和m条边的权值(单向),求一个环的回路中,总点权与总边权的最大比例(最优比例环)
思路
因为Dinkelbach需要分别记录环的总边权和总点权,所以不好写,还是用二分吧
注意点
(1)精度不能太高,设为1e-5就会TLE,精度为1e-3的时候,l和r的值不一样,要输出l,精度为1e-4时随意
(2) r的初始值设为1e9和2e9就会超时,但是设为INF(0x3f3f3f3f)就可以A,明明INF介于两者之间
(3) 题目数据都是可以形成环的,不需要判断也可以
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const int N = 1111;
const int M = N * N;
const int INF = 0x3f3f3f3f;
struct point{
int to,nex;
double cost;
}e[5 * N];
double value[N];
int head[N],cnt;
double lowcost[N];
bool vis[N];
int num[N];
void add(int u,int v,double cost)
{
e[cnt].cost = cost;
e[cnt].to = v;
e[cnt].nex = head[u];
head[u] = cnt++;
}
bool Binary(int n,double r)
{
queue<int> q;
memset(num,0,sizeof(num));
memset(vis,false,sizeof(vis));
for (int i = 1; i <= n; i++){
lowcost[i] = 1.0 * INF;
}
lowcost[1] = 0;
q.push(1);
while(!q.empty()){
int u = q.front();
q.pop();
vis[u] = false;
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (lowcost[v] > lowcost[u] + r * e[i].cost - 1.0 * value[v]){
lowcost[v] = lowcost[u] + r * e[i].cost - 1.0 * value[v];
if (!vis[v]){
vis[v] = true;
q.push(v);
num[v]++;
if (num[v] > n) return true;
}
}
}
}
return false;
}
int main()
{
int n,m;
while(scanf("%d %d",&n,&m) == 2){
for (int i = 1; i <= n; i++){
scanf("%lf",&value[i]);
}
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 0; i < m; i++){
int u,v;
double cost;
scanf("%d %d %lf",&u,&v,&cost);
add(u,v,cost);
}
double l = 0,r = N * N;
while(r - l >= 1e-3){
double mid = (l + r) / 2;
if(Binary(n,mid)){
l = mid + eps;
}
else r = mid - eps;
}
if (l > 0) printf("%.2f\n",l);
else printf("0\n");
}
return 0;
}
POJ 3155 题目链接
参考 算法合集之《最小割模型在信息学竞赛中的应用》
题意 最大密度子图(204ms)
一个公司有n个人,给出了一些有冲突的人的对数(u,v),公司决定裁人,那么总裁现在要裁掉冲突率最高的那些人(冲突率=在这些人中存在的冲突数/人数)。
思路1 最大权闭合子图
因
为
边
依
赖
于
点
,
所
以
就
可
以
转
化
为
最
大
权
闭
合
子
图
模
型
来
求
解
因为边依赖于点,所以就可以转化为最大权闭合子图模型来求解
因为边依赖于点,所以就可以转化为最大权闭合子图模型来求解
二
分
值
为
g
时
,
有
h
(
g
)
=
m
a
x
(
∣
E
′
∣
−
g
∣
V
′
∣
)
,
选
择
(
u
,
v
)
∈
∣
E
′
∣
,
则
有
u
,
v
∈
∣
V
′
∣
二分值为g时,有h(g) = max(|E'|-g|V'|),选择(u,v)\in|E'|,则有u,v\in|V'|
二分值为g时,有h(g)=max(∣E′∣−g∣V′∣),选择(u,v)∈∣E′∣,则有u,v∈∣V′∣
将
边
e
(
u
,
v
)
看
成
点
v
e
,
构
造
最
大
权
闭
合
子
图
模
型
;
将边e(u,v)看成点v_e,构造最大权闭合子图模型;
将边e(u,v)看成点ve,构造最大权闭合子图模型;
将
原
图
中
的
边
当
成
新
点
连
到
超
级
源
点
,
将
原
图
中
的
点
连
到
超
级
汇
点
将原图中的边当成新点连到超级源点,将原图中的点连到超级汇点
将原图中的边当成新点连到超级源点,将原图中的点连到超级汇点
构
成
新
图
(
S
t
a
r
t
,
v
e
,
1
)
,
(
v
e
,
u
,
I
N
F
)
,
(
v
e
,
v
,
I
N
F
)
,
(
u
,
E
N
D
,
g
)
,
(
v
,
E
N
D
,
g
)
构成新图(Start,v_e,1),(v_e,u,INF),(v_e,v,INF),(u,END,g),(v,END,g )
构成新图(Start,ve,1),(ve,u,INF),(ve,v,INF),(u,END,g),(v,END,g)
最大流算法(ISAP模板 )
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 2222;
const int M = 2e5 + 5;
const int INF = 0x3f3f3f3f;
struct node{
int to,nex;
double len;
}e[M];
struct point{
int u,v;
}p[N];
int depth[N],gap[N];
int se,ed,V,n,m;
int head[N],cnt;
bool vis[N];
int sum;
void add(int u,int v,double len)
{
e[cnt].to = v;
e[cnt].len = len;
e[cnt].nex = head[u];
head[u] = cnt++;
}
/// 逆序BFS
void bfs()
{
memset(gap,0,sizeof(gap));
memset(depth,-1,sizeof(depth));
queue<int> q;
q.push(ed);
depth[ed] = 0;
gap[0] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] != -1) continue;
depth[v] = depth[u] + 1;
gap[depth[v]]++;
q.push(v);
}
}
}
double dfs(int u,double maxflow)
{
if (u == ed) return maxflow;
double ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[u] == depth[v] + 1 && e[i].len > 0) {
double temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow;
e[i ^ 1].len += temflow;
ans += temflow;
if (ans == maxflow) return ans;
}
}
gap[depth[u]]--;
if (!gap[depth[u]]) depth[se] = V + 1;
depth[u]++;
gap[depth[u]]++;
return ans;
}
double isap()
{
double ans = 0;
bfs();
while(depth[se] < V){
ans += dfs(se,INF);
}
return 1.0 * m - ans;
}
void build(double g)
{
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 1; i <= n; i++){
add(i,ed,g);
add(ed,i,0);
}
for (int i = 0; i < m; i++){
add(se,n + i + 1,1);
add(n + i + 1,se,0);
add(n + i + 1,p[i].u,INF);
add(p[i].u,n + i + 1,0);
add(n + i + 1,p[i].v,INF);
add(p[i].v,n + i + 1,0);
}
}
void Find(int u)
{
vis[u] = true;
if (u >= 1 && u <= n) sum++;
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (vis[v] || e[i].len <= 0) continue;
Find(v);
}
}
int main()
{
while(scanf("%d %d",&n,&m) == 2){
if (m == 0){
printf("1\n1\n");
continue;
}
for (int i = 0; i < m; i++){
scanf("%d %d",&p[i].u,&p[i].v);
}
se = 0,ed = n + m + 1,V = ed + 1;
double l = 0,r = m;
while(r - l >= 1.0 / n / n){
double mid = (l + r) / 2;
build(mid);
if (isap() < eps) r = mid; /// 不用加eps。不然会wa
else l = mid;
}
build(l);
isap();
memset(vis,false,sizeof(vis));
sum = 0;
Find(se);
printf("%d\n",sum);
for (int i = 1; i <= n; i++){
if (vis[i]) printf("%d\n",i);
}
}
return 0;
}
思路2 导出子图最小割 (141ms)
对
于
点
集
∣
V
′
∣
,
∣
E
′
∣
=
1
2
(
∑
v
∈
V
′
d
v
−
C
[
V
′
,
V
′
‾
]
)
对于点集|V'|,|E'| ={{1} \over {2}}(\sum_{v\in V'}{d_v}-C[V', \overline{V'}] )
对于点集∣V′∣,∣E′∣=21(v∈V′∑dv−C[V′,V′])
d
v
表
示
v
的
度
,
C
[
V
′
,
V
′
‾
]
表
示
于
V
′
关
联
但
不
是
E
′
中
的
边
的
数
量
d_v表示v的度,C[V', \overline{V'}]表示于V'关联但不是E'中的边的数量
dv表示v的度,C[V′,V′]表示于V′关联但不是E′中的边的数量
将
最
大
化
转
化
成
最
小
化
,
有
−
h
(
g
)
=
m
i
n
(
g
∣
V
′
∣
−
∣
E
′
∣
)
将最大化转化成最小化,有-h(g) = min(g|V'|-|E'|)
将最大化转化成最小化,有−h(g)=min(g∣V′∣−∣E′∣)
=
1
2
(
∑
v
∈
V
′
(
2
g
−
d
v
)
+
C
[
V
′
,
V
′
‾
]
)
= {{1} \over {2}}(\sum_{v\in V'}{(2g-d_v)}+C[V', \overline{V'}] )
=21(v∈V′∑(2g−dv)+C[V′,V′])
因
为
最
大
流
要
是
非
负
的
流
量
,
所
以
要
加
上
足
够
大
的
数
因为最大流要是非负的流量,所以要加上足够大的数
因为最大流要是非负的流量,所以要加上足够大的数
(
U
可
以
取
m
,
U
的
目
的
是
用
来
使
得
2
∗
g
−
d
的
值
始
终
为
正
)
(U可以取m,U的目的是用来使得2*g-d的值始终为正)
(U可以取m,U的目的是用来使得2∗g−d的值始终为正)
构
图
(
S
t
a
r
t
,
u
,
U
)
,
(
S
t
a
r
t
,
v
,
U
)
,
(
v
,
u
,
1
)
,
(
u
,
v
,
1
)
,
(
u
,
E
N
D
,
2
g
−
d
u
)
,
(
v
,
E
N
D
,
2
g
−
d
v
)
构图(Start,u,U),(Start,v,U),(v,u,1),(u,v,1),(u,END,2g-d_u),(v,END,2g-d_v )
构图(Start,u,U),(Start,v,U),(v,u,1),(u,v,1),(u,END,2g−du),(v,END,2g−dv)
所
以
h
(
g
)
=
−
最
小
割
−
U
∗
n
2
所以h(g) = -{{最小割-U*n} \over {2}}
所以h(g)=−2最小割−U∗n
二分找到最优值即为mid ,但是如果要求图中的点则需要用left来从新图求最大流之后然后从源点开始dfs遍历,最后得出结果
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 2222;
const int M = 2e5 + 5;
const int INF = 0x3f3f3f3f;
struct node{
int to,nex;
double len;
}e[M];
struct point{
int u,v;
}p[N];
int depth[N],gap[N];
int se,ed,V,n,m;
int head[N],cnt;
bool vis[N];
int sum,D[N];
void add(int u,int v,double len)
{
e[cnt].to = v;
e[cnt].len = len;
e[cnt].nex = head[u];
head[u] = cnt++;
}
/// 层次网络
void bfs()
{
memset(gap,0,sizeof(gap));
memset(depth,-1,sizeof(depth));
queue<int> q;
q.push(ed);
depth[ed] = 0;
gap[0] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (depth[v] != -1) continue;
depth[v] = depth[u] + 1;
gap[depth[v]]++;
q.push(v);
}
}
}
/// dfs查找所有增广路并做流量调整
double dfs(int u,double maxflow)
{
if (u == ed) return maxflow;
double ans = 0;
for (int i = head[u]; i != -1 && ans < maxflow; i = e[i].nex){
int v = e[i].to;
if (depth[u] == depth[v] + 1 && e[i].len > 0) {
double temflow = dfs(v,min(e[i].len,maxflow - ans));
e[i].len -= temflow; /// 前向弧流量减少
e[i ^ 1].len += temflow; /// 反向弧流量增加
ans += temflow;
if (ans == maxflow) return ans;
}
}
gap[depth[u]]--;
if (!gap[depth[u]]) depth[se] = V + 1;
depth[u]++;
gap[depth[u]]++;
return ans;
}
double isap()
{
double ans = 0;
bfs();
while(depth[se] < V){
ans += dfs(se,INF);
}
return ans;
}
void build(double g)
{
memset(head,-1,sizeof(head));
cnt = 0;
for (int i = 1; i <= n; i++){
add(i,ed,2 * g - D[i]);
add(ed,i,0);
add(se,i,m);
add(i,se,0);
}
for (int i = 0; i < m; i++){
add(p[i].u,p[i].v,1);
add(p[i].v,p[i].u,0);
add(p[i].v,p[i].u,1);
add(p[i].u,p[i].v,0);
}
}
void Find(int u)
{
vis[u] = true;
sum++;
for (int i = head[u]; i != -1; i = e[i].nex){
int v = e[i].to;
if (vis[v] || e[i].len <= 0) continue;
Find(v);
}
}
int main()
{
while(scanf("%d %d",&n,&m) == 2){
if (m == 0){
printf("1\n1\n");
continue;
}
memset(D,0,sizeof(D));
for (int i = 0; i < m; i++){
scanf("%d %d",&p[i].u,&p[i].v);
D[p[i].u]++;
D[p[i].v]++;
}
se = 0,ed = n + 1,V = ed + 1;
double l = 0,r = m;
while(r - l >= 1.0 / n / n){
double mid = (l + r) / 2;
build(mid);
if ((n * m * 1.0 - isap()) / 2 > eps) l = mid;
else r = mid;
}
build(l);
isap();
memset(vis,false,sizeof(vis));
sum = 0;
Find(se);
printf("%d\n",sum - 1);
for (int i = 1; i <= n; i++){
if (vis[i]) printf("%d\n",i);
}
}
return 0;
}