[SCOI2011] 糖果

GitHub跳转原题关系图返回列表

把下界约束建成 0/1 边,SCC 判严格环后在缩点 DAG 上求最长路。

OJ: luogu

题目 ID: P3275

难度:提高

标签:差分约束强连通分量DAGpython

日期: 2026-07-17 03:00

题意

满足五类大小关系且每人至少一颗糖,求最小糖果总数。

思路

约束写成 x_v >= x_u+w,其中严格大于是权 1,否则权 0。若同一 SCC 内存在权 1 边,就形成正环而无解;否则缩点图是 DAG,从每个分量初值 1 做最长路,分量内节点取相同最小值。

Python 知识

  • 显式栈实现 Kosaraju 两遍 DFS,避免递归深度问题。
  • deque 做缩点 DAG 的拓扑排序。
  • 分量大小乘分量最长路值,直接得到总糖果数。

代码

python
import sys
from collections import deque


input = sys.stdin.buffer.readline
n, constraints = map(int, input().split())
graph = [[] for _ in range(n + 1)]
reverse_graph = [[] for _ in range(n + 1)]
edges = []


def add_edge(u, v, weight):
    graph[u].append(v)
    reverse_graph[v].append(u)
    edges.append((u, v, weight))


possible = True
for _ in range(constraints):
    kind, a, b = map(int, input().split())
    if kind == 1:
        add_edge(a, b, 0)
        add_edge(b, a, 0)
    elif kind == 2:
        possible &= a != b
        add_edge(a, b, 1)
    elif kind == 3:
        add_edge(b, a, 0)
    elif kind == 4:
        possible &= a != b
        add_edge(b, a, 1)
    else:
        add_edge(a, b, 0)

if possible:
    visited = bytearray(n + 1)
    finish = []
    for start in range(1, n + 1):
        if visited[start]:
            continue
        visited[start] = 1
        stack = [(start, 0)]
        while stack:
            node, index = stack[-1]
            if index < len(graph[node]):
                neighbor = graph[node][index]
                stack[-1] = node, index + 1
                if not visited[neighbor]:
                    visited[neighbor] = 1
                    stack.append((neighbor, 0))
            else:
                finish.append(node)
                stack.pop()
    component = [0] * (n + 1)
    component_count = 0
    for start in reversed(finish):
        if component[start]:
            continue
        component_count += 1
        component[start] = component_count
        stack = [start]
        while stack:
            node = stack.pop()
            for neighbor in reverse_graph[node]:
                if component[neighbor] == 0:
                    component[neighbor] = component_count
                    stack.append(neighbor)
    size = [0] * (component_count + 1)
    dag = [[] for _ in range(component_count + 1)]
    indegree = [0] * (component_count + 1)
    for node in range(1, n + 1):
        size[component[node]] += 1
    for u, v, weight in edges:
        first, second = component[u], component[v]
        if first == second:
            if weight:
                possible = False
                break
        else:
            dag[first].append((second, weight))
            indegree[second] += 1

if not possible:
    print(-1)
else:
    value = [1] * (component_count + 1)
    queue = deque(node for node in range(1, component_count + 1) if indegree[node] == 0)
    while queue:
        node = queue.popleft()
        for neighbor, weight in dag[node]:
            value[neighbor] = max(value[neighbor], value[node] + weight)
            indegree[neighbor] -= 1
            if indegree[neighbor] == 0:
                queue.append(neighbor)
    print(sum(value[node] * size[node] for node in range(1, component_count + 1)))

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-17 01:40
 * update_at: 2026-07-17 01:40
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

SCC 与 DAG DP 均为 O(n+m),空间 O(n+m)

总结

0/1 下界约束中,矛盾恰好是强连通分量里出现严格增长边。