P3357 最長k可重線段集問題
題目描述
給定平面?x-O-yx?O?y?上?nn?個開線段組成的集合?II,和一個正整數?kk?。試設計一個算法,從開線段集合?II?中選取出開線段集合?S\subseteq IS?I?,使得在?xx?軸上的任何一點?pp,SS?中與直線?x=px=p?相交的開線段個數不超過?kk,且\sum\limits_{z\in S}|z|z∈S∑?∣z∣達到最大。這樣的集合?SS?稱為開線段集合?II?的最長?kk?可重線段集。\sum\limits_{z\in S}|z|z∈S∑?∣z∣?稱為最長?kk?可重線段集的長度。
對于任何開線段?zz,設其斷點坐標為?(x_0,y_0)(x0?,y0?)?和?(x_1,y_1)(x1?,y1?),則開線段?zz?的長度?|z|∣z∣?定義為:|z|=\lfloor\sqrt{(x_1-x_0)^2+(y_1-y_0)^2}\rfloor∣z∣=?(?
對于給定的開線段集合?II?和正整數?kk,計算開線段集合?II?的最長?kk?可重線段集的長度。
輸入輸出格式
輸入格式:
?
文件的第一 行有?22?個正整數?nn?和?kk,分別表示開線段的個數和開線段的可重疊數。
接下來的?nn?行,每行有?44?個整數,表示開線段的?22?個端點坐標。
?
輸出格式:
?
程序運行結束時,輸出計算出的最長?kk?可重線段集的長度。
?
輸入輸出樣例
4 2
1 2 7 3
6 5 8 3
7 8 10 5
9 6 13 9
17
說明
1\leq n\leq5001≤n≤500
1 \leq k \leq 131≤k≤13
?
?
?
這個題目和之前的最長k可重區間集問題是一樣的,就是把平面上的線段投影到x軸,但是呢,有一個點有問題,就是要
特判兩條直線重合且垂直于x軸的這一種情況,具體是為什么呢,我也有點不明白為什么了,好像是會出現環的情況。
?
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <vector>
#include <iostream>
#include <algorithm>
#include <map>
#include <cstring>
#include <cmath>
#include <string>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5;
struct edge
{int u, v, c, f;ll cost;edge(int u, int v, int c, int f, ll cost) :u(u), v(v), c(c), f(f), cost(cost) {}
};
vector<edge>e;
vector<int>G[maxn];
int a[maxn];//找增廣路每個點的水流量
int p[maxn];//每次找增廣路反向記錄路徑
int d[maxn];//SPFA算法的最短路
int inq[maxn];//SPFA算法是否在隊列中
int s, t;
void init(int n)
{for (int i = 0; i <= n; i++)G[i].clear();e.clear();
}
void add(int u, int v, int c, ll cost)
{e.push_back(edge(u, v, c, 0, cost));e.push_back(edge(v, u, 0, 0, -cost));int m = e.size();G[u].push_back(m - 2);G[v].push_back(m - 1);
}
bool bellman(int s, int t, int& flow, long long & cost)
{memset(d, 0xef, sizeof(d));memset(inq, 0, sizeof(inq));d[s] = 0; inq[s] = 1;//源點s的距離設為0,標記入隊p[s] = 0; a[s] = INF;//源點流量為INF(和之前的最大流算法是一樣的)
queue<int>q;//Bellman算法和增廣路算法同步進行,沿著最短路拓展增廣路,得出的解一定是最小費用最大流
q.push(s);while (!q.empty()){int u = q.front();q.pop();inq[u] = 0;//入隊列標記刪除for (int i = 0; i < G[u].size(); i++){edge & now = e[G[u][i]];int v = now.v;if (now.c > now.f && d[v] < d[u] + now.cost)//now.c > now.f表示這條路還未流滿(和最大流一樣)//d[v] > d[u] + e.cost Bellman 算法中邊的松弛
{d[v] = d[u] + now.cost;//Bellman 算法邊的松弛p[v] = G[u][i];//反向記錄邊的編號a[v] = min(a[u], now.c - now.f);//到達v點的水量取決于邊剩余的容量和u點的水量if (!inq[v]) { q.push(v); inq[v] = 1; }//Bellman 算法入隊
}}}if (d[t] < 0)return false;//找不到增廣路flow += a[t];//最大流的值,此函數引用flow這個值,最后可以直接求出flowcost += (long long)d[t] * (long long)a[t];//距離乘上到達匯點的流量就是費用for (int u = t; u != s; u = e[p[u]].u)//逆向存邊
{e[p[u]].f += a[t];//正向邊加上流量e[p[u] ^ 1].f -= a[t];//反向邊減去流量 (和增廣路算法一樣)
}return true;
}
int MaxcostMaxflow(int s, int t, long long & cost)
{cost = 0;int flow = 0;while (bellman(s, t, flow, cost));//由于Bellman函數用的是引用,所以只要一直調用就可以求出flow和costreturn flow;//返回最大流,cost引用可以直接返回最小費用
}struct node
{int xx1, yy1, xx2, yy2;ll cost;
}exa[maxn];
bool cmp(node a, node b)
{return a.xx1 < b.xx1;
}ll dis(int x, int y, int x1, int y1)
{return sqrt((x - x1) * 1ll * (x - x1) + (y - y1) * 1ll * (y - y1));
}int main()
{int n, m;cin >> n >> m;int s1 = 1;s = 0, t = 2 * n + 3;for (int i = 1; i <= n; i++){cin >> exa[i].xx1 >> exa[i].yy1 >> exa[i].xx2 >> exa[i].yy2;if (exa[i].xx1 > exa[i].xx2){swap(exa[i].xx1, exa[i].xx2);swap(exa[i].yy1, exa[i].yy2);}exa[i].cost = dis(exa[i].xx1, exa[i].yy1, exa[i].xx2, exa[i].yy2);}sort(exa + 1, exa + 1 + n, cmp);add(s, s1, m, 0);for (int i = 1; i <= n; i++){add(s1, 1 + 2 * i - 1, 1, 0);add(1 + 2 * i - 1, 1 + 2 * i, 1, exa[i].cost);add(1 + 2 * i, t, 1, 0);for (int j = 1; j < i; j++){if (exa[j].xx2 == exa[i].xx1&&exa[j].xx1 == exa[j].xx2&&exa[i].xx1==exa[i].xx2) continue;if (exa[j].xx2 <= exa[i].xx1) add(1 + 2 * j, 1 + 2 * i - 1, 1, 0);}}ll cost = 0;int ans = MaxcostMaxflow(s, t, cost);printf("%lld\n", cost);return 0;
}
?