解题思路

这是一道关于生存博弈的图论问题。主要思路如下:

  1. 问题分析:

    • 个狙击手,每人瞄准一个目标
    • 需要找出最少和最多生存人数
    • 可以将问题转化为有向图问题
    • 每个狙击手是一个节点,瞄准关系构成边
  2. 优化思路:

    • 最大生存数:优先处理入度为0的节点
    • 最小生存数:处理环和链的特殊情况
    • 使用father数组记录入度
    • 使用visit数组标记访问状态
  3. 关键点:

    • 处理无入度节点(必定生存)
    • 处理自环节点(必定生存)
    • 处理普通环(可以最大/最小化生存数)

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000005;
int target[MAXN];    // 目标数组
bool visit[MAXN];    // 访问标记
int father[MAXN];    // 入度数组

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int n, ans;
    cin >> n;
    
    // 读入数据并统计入度
    for(int i = 1; i <= n; i++) {
        cin >> target[i];
        father[target[i]]++;
    }
    
    // 计算最大生存数
    ans = 0;
    for(int i = 1; i <= n; i++) {
        if(father[i] == 0) {  // 处理入度为0的节点
            visit[i] = true;
            ans++;
            int next = target[i];
            while(!visit[next]) {
                visit[next] = true;
                next = target[next];
            }
        }
    }
    
    // 处理剩余的环
    for(int i = 1; i <= n; i++) {
        if(visit[i] || i == target[i]) continue;
        visit[i] = true;
        ans++;
        int next = target[i];
        while(!visit[next]) {
            visit[next] = true;
            next = target[next];
        }
    }
    cout << ans << endl;
    
    // 计算最小生存数
    memset(visit, false, sizeof(visit));
    ans = 0;
    queue<int> q;
    
    // 将入度为0的节点入队
    for(int i = 1; i <= n; i++) {
        if(father[i] == 0) q.push(i);
    }
    
    // 处理入度为0的节点
    while(!q.empty()) {
        int curr = q.front();
        q.pop();
        visit[curr] = true;
        ans++;
        int next = target[curr];
        if(!visit[next]) {
            visit[next] = true;
            next = target[next];
            father[next]--;
            if(father[next] == 0) {
                q.push(next);
            }
        }
    }
    
    // 处理剩余的环
    for(int i = 1; i <= n; i++) {
        if(visit[i] || i == target[i]) continue;
        visit[i] = true;
        int len = 1;
        int next = target[i];
        while(!visit[next]) {
            len++;
            visit[next] = true;
            next = target[next];
        }
        ans += (len >> 1);  // 环中最少生存人数为长度的一半
    }
    cout << ans << endl;
    
    return 0;
}
import java.io.*;
import java.util.*;

public class Main {
    static final int MAXN = 1000005;
    static int[] target = new int[MAXN];
    static boolean[] visit = new boolean[MAXN];
    static int[] father = new int[MAXN];
    
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        StringTokenizer st = new StringTokenizer(br.readLine());
        
        // 读入数据并统计入度
        for(int i = 1; i <= n; i++) {
            target[i] = Integer.parseInt(st.nextToken());
            father[target[i]]++;
        }
        
        // 计算最大生存数
        int ans = getMaxSurvivors(n);
        System.out.println(ans);
        
        // 计算最小生存数
        Arrays.fill(visit, false);
        ans = getMinSurvivors(n);
        System.out.println(ans);
    }
    
    static int getMaxSurvivors(int n) {
        int ans = 0;
        // 处理入度为0的节点
        for(int i = 1; i <= n; i++) {
            if(father[i] == 0) {
                visit[i] = true;
                ans++;
                int next = target[i];
                while(!visit[next]) {
                    visit[next] = true;
                    next = target[next];
                }
            }
        }
        
        // 处理剩余的环
        for(int i = 1; i <= n; i++) {
            if(visit[i] || i == target[i]) continue;
            visit[i] = true;
            ans++;
            int next = target[i];
            while(!visit[next]) {
                visit[next] = true;
                next = target[next];
            }
        }
        return ans;
    }
    
    static int getMinSurvivors(int n) {
        int ans = 0;
        Queue<Integer> q = new LinkedList<>();
        
        // 将入度为0的节点入队
        for(int i = 1; i <= n; i++) {
            if(father[i] == 0) q.offer(i);
        }
        
        // 处理入度为0的节点
        while(!q.isEmpty()) {
            int curr = q.poll();
            visit[curr] = true;
            ans++;
            int next = target[curr];
            if(!visit[next]) {
                visit[next] = true;
                next = target[next];
                father[next]--;
                if(father[next] == 0) {
                    q.offer(next);
                }
            }
        }
        
        // 处理剩余的环
        for(int i = 1; i <= n; i++) {
            if(visit[i] || i == target[i]) continue;
            visit[i] = true;
            int len = 1;
            int next = target[i];
            while(!visit[next]) {
                len++;
                visit[next] = true;
                next = target[next];
            }
            ans += (len >> 1);
        }
        return ans;
    }
}
from collections import deque

def get_max_survivors(n, target, father, visit):
    ans = 0
    # 处理入度为0的节点
    for i in range(1, n + 1):
        if father[i] == 0:
            visit[i] = True
            ans += 1
            next_node = target[i]
            while not visit[next_node]:
                visit[next_node] = True
                next_node = target[next_node]
    
    # 处理剩余的环
    for i in range(1, n + 1):
        if visit[i] or i == target[i]:
            continue
        visit[i] = True
        ans += 1
        next_node = target[i]
        while not visit[next_node]:
            visit[next_node] = True
            next_node = target[next_node]
    return ans

def get_min_survivors(n, target, father, visit):
    ans = 0
    q = deque()
    
    # 将入度为0的节点入队
    for i in range(1, n + 1):
        if father[i] == 0:
            q.append(i)
    
    # 处理入度为0的节点
    while q:
        curr = q.popleft()
        visit[curr] = True
        ans += 1
        next_node = target[curr]
        if not visit[next_node]:
            visit[next_node] = True
            next_node = target[next_node]
            father[next_node] -= 1
            if father[next_node] == 0:
                q.append(next_node)
    
    # 处理剩余的环
    for i in range(1, n + 1):
        if visit[i] or i == target[i]:
            continue
        visit[i] = True
        length = 1
        next_node = target[i]
        while not visit[next_node]:
            length += 1
            visit[next_node] = True
            next_node = target[next_node]
        ans += length >> 1
    return ans

n = int(input())
target = [0] + list(map(int, input().split()))
father = [0] * (n + 1)
visit = [False] * (n + 1)

# 统计入度
for i in range(1, n + 1):
    father[target[i]] += 1

# 计算最大和最小生存数
print(get_max_survivors(n, target, father, visit))
visit = [False] * (n + 1)
print(get_min_survivors(n, target, father, visit))

算法分析

图论分析:

  1. 入度为0的点:

    • 必定能存活
    • 可以影响其目标的生存
  2. 环的处理:

    • 自环:必定存活
    • 普通环:
      • 最大生存:可以全部存活
      • 最小生存:只能存活一半

复杂度分析:

  • 时间复杂度:
    • 每个节点最多访问一次
  • 空间复杂度:
    • 需要存储目标数组和访问标记