摘蘋果
有N堆蘋果,每堆蘋果有x,y,w三個屬性,每次能夠進行的操作是:把一堆蘋果移動到另一堆蘋果中,移動蘋果所需要花費的力氣為$w \times (|x_1-x_2|+|y_1-y_2|$ ,問最少需要花費多少力氣才能把這些蘋果移動到一堆去?
暴力解法復雜度為$O(n^2)$,問題等價於尋找一個中心點。
這個問題關鍵在於距離的定義比較特殊。如果距離定義為歐式距離,這個問題就變得非常艱難(比如費馬點),這是一個NP問題。
因為距離定義比較特殊,所以x方向和y方向是完全無關的。可以分開考慮這兩者。首先把這個問題看做一維來考慮。
引題
一條路上分布着N個村庄,每個村庄都有一個坐標,現在要在這條路上修建一個水站,使得這N個村庄到水站的距離之和最短。
這個問題的結論是:把水站建立在中位數上。如果村庄個數為奇數個,那么正好有一個中位數;如果村庄個數為偶數個,那么水站建立在中間兩個村庄之間。
回到本題中來,因為x和y是無關的,所以可以先求出最優的x和最優的y。這樣一來,把全部蘋果移動到x,y處是最恰當的,但是只能把蘋果移動到有蘋果的點,所以只能找x,y附近的一些點,這是因為整個二維平面是一個凸面,必然只有一個最小點。只需要枚舉離中間點最近的幾個點即可,這么做當然是不嚴謹的。很容易舉出反例來。
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
public class Main {
final int MAXN = (int) (1e5 + 7);
class Point {
long x, y, w;
Point(int x, int y, int w) {
this.x = x;
this.y = y;
this.w = w;
}
}
Point find(Point[] a, long total) {
int cnt = 0;
long half = total >> 1;
for (Point i : a) {
cnt += i.w;
if (cnt >= half) {
return i;
}
}
return a[a.length - 1];
}
Main() {
Scanner cin = new Scanner(System.in);
int N = cin.nextInt();
Point[] a = new Point[N];
long total = 0;
for (int i = 0; i < N; i++) {
a[i] = new Point(cin.nextInt(), cin.nextInt(), cin.nextInt());
total += a[i].w;
}
Arrays.sort(a, Comparator.comparingInt(m -> (int) (m.x)));
long x = find(a, total).x;
Arrays.sort(a, Comparator.comparingInt(m -> (int) (m.y)));
long y = find(a, total).y;
Arrays.sort(a, Comparator.comparingInt(m -> (int) (Math.abs(m.x - x) + Math.abs(m.y - y))));
long ans = Long.MAX_VALUE;
for (int j = 0; j < Math.min(50, N); j++) {
long s = 0;
for (Point i : a) {
s += i.w * (Math.abs(i.x - a[j].x) + Math.abs(i.y - a[j].y));
}
ans = Math.min(ans, s);
}
System.out.println(ans);
}
public static void main(String[] args) {
new Main();
}
}
狙擊手
N個狙擊手,每個狙擊手瞄准一個人(這個人有可能是他自己!)。所有狙擊手不會同時開槍。你可以隨意安排所有狙擊手開槍的次序,直到無法再開槍為止,問:經過一番廝殺之后,最多幸存幾個狙擊手、最少幸存幾個狙擊手?
這個問題是兩問:一問是狙擊手最多活幾個,一問是狙擊手最少活幾個。
一眼看上去,狙擊手之間構成的結構是圖。而實際上,這個圖非常特殊:每個狙擊手只能瞄准一個人,所以每個結點的出度必然是1,而入度可以很多。
首先考慮最多幸存幾個狙擊手。很顯然,那些未被瞄准的人必然會幸存下來。但是這些人是一定要開槍的。這些“必然不死者”亂槍過后,會拯救一批新的“必然不死者”,“必然不死者”開槍之后又會拯救不死者,這樣一直到全部“不死者”都無法開槍為止。所以這個問題可以用優先隊列來解決:優先讓必然不死者開槍(他們是一定要開槍的)。
接下來考慮最少幸存幾個狙擊手。這就需要深刻理解這個問題的特殊之處:每個結點出度為1。需要看清楚“單出度圖”的一個特點:如果含有環,只能是形如“0”和形如“6”這樣的環,不可能出現形如“8”的環。而每個形如6的環最少幸存一個人。於是,問題轉化為:整個圖中有多少個“6”。
import java.util.*;
public class Main {
class Node {
int id, cnt;
Node(int id, int cnt) {
this.id = id;
this.cnt = cnt;
}
}
int nonzero(boolean died[]) {
int s = 0;
for (int i = 1; i < died.length; i++) {
if (!died[i]) s++;
}
return s;
}
Main() {
Scanner cin = new Scanner(System.in);
int N = cin.nextInt();
int a[] = new int[N + 1];
int b[] = new int[N + 1];//bi表示想殺i的人的個數
for (int i = 1; i <= N; i++) {
a[i] = cin.nextInt();
}
for (int i = 1; i <= N; i++) b[a[i]]++;
PriorityQueue<Node> q = new PriorityQueue<>(Comparator.comparing(x -> x.cnt));
for (int i = 1; i <= N; i++) {
q.add(new Node(i, b[i]));
}
boolean died[] = new boolean[N + 1];
for (int i = 1; i <= N; i++) if (a[i] == i) died[i] = true;
while (!q.isEmpty()) {
int now = q.poll().id;
if (died[now]) continue;
if (!died[a[now]]) {
died[a[now]] = true;
int next = a[a[now]];//我的下下家得到解放
b[next]--;
q.add(new Node(next, b[next]));
}
}
int maxLive = nonzero(died);
Arrays.fill(died, false);
for (int i = 1; i <= N; i++) if (a[i] == i) died[i] = true;
int ring[] = new int[N + 1];//ring i是否在環上
for (int i = 1; i <= N; i++) ring[i] = i;
for (int i = 1; i <= N; i++) {
if (died[i]) continue;
int now = i;
while (!died[a[now]] && a[now] != i) {
died[a[now]] = true;
now = a[now];
}
if (a[now] == i) {//如果成環了,要讓環的祖先承擔后續損失
int j = i;
while (true) {
ring[j] = i;
j = a[j];
if (j == i) break;
}
}
if (ring[a[now]] != a[now]) {
died[ring[a[now]]] = true;
}
}
int minLive = nonzero(died);
System.out.println(minLive);
System.out.println(maxLive);
}
public static void main(String[] args) {
new Main();
}
}
最小區間
給定K個長度為N的數組,要求一個最小區間(區間長度盡量小),要求這個最小區間包含的數字跟K個數組中任一數組的交集都不為空。
優先隊列+單調隊列很容易處理。
import java.util.*;
public class Main {
class Point {
int x, k, index;
Point(int x, int k, int index) {
this.x = x;
this.k = k;
this.index = index;
}
}
Main() {
Scanner cin = new Scanner(System.in);
int K = cin.nextInt();
int N = cin.nextInt();
PriorityQueue<Point> q = new PriorityQueue<>(Comparator.comparing(x -> x.x));
for (int i = 0; i < K; i++) {
for (int j = 0; j < N; j++) {
int x = cin.nextInt();
q.add(new Point(x, i, j));
}
}
int[] inqCount = new int[K];
Queue<Point> qq = new LinkedList<>();//單調隊列
int nonzeroCount = 0;
int ans = Integer.MAX_VALUE;
int beg = 0, end = 0;
boolean startCheck = false;//是否開始覆蓋
while (!q.isEmpty()) {
Point now = q.poll();
qq.add(now);
inqCount[now.k]++;
if (inqCount[now.k] == 1) {
nonzeroCount++;
if (nonzeroCount == K) {
startCheck = true;
}
}
//彈出無用元素
while (!qq.isEmpty() && inqCount[qq.peek().k] > 1) {
inqCount[qq.peek().k]--;
qq.poll();
}
if (startCheck) {
int minValue = qq.peek().x;
int nowAns = now.x - minValue;
if (nowAns < ans) {
ans = nowAns;
beg = minValue;
end = now.x;
}
}
}
System.out.println(beg + " " + end);
}
public static void main(String[] args) {
new Main();
}
}
