这道题目来自NOI1999,要求构造一个长度为N的01串,满足两个约束条件:
首先我们需要将题目中的自然语言描述转化为数学表达式。设S = s₁s₂...sₙ是一个01串,其中sᵢ ∈ {0,1}。
对于第一个约束条件,可以表示为:对于所有1 ≤ j ≤ N-L₀+1,
A₀ ≤ ∑_{k=j}^{j+L₀-1} (1 - sₖ) ≤ B₀
同理,第二个约束条件可以表示为:对于所有1 ≤ j ≤ N-L₁+1,
A₁ ≤ ∑_{k=j}^{j+L₁-1} sₖ ≤ B₁
这类带有区间约束的问题,通常可以考虑使用差分约束系统来解决。差分约束系统是一种特殊的线性规划问题,其中每个约束条件都可以表示为变量的差值关系。
设前缀和数组为sum[i] = ∑_{k=1}^i sₖ,那么我们可以将原始约束转化为关于sum数组的不等式:
对于0的约束:
A₀ ≤ (L₀ - (sum[j+L₀-1] - sum[j-1])) ≤ B₀
可以转化为:
sum[j+L₀-1] - sum[j-1] ≤ L₀ - A₀
sum[j-1] - sum[j+L₀-1] ≤ B₀ - L₀
对于1的约束:
A₁ ≤ sum[j+L₁-1] - sum[j-1] ≤ B₁
此外,由于每个字符只能是0或1,我们还有基本约束:
0 ≤ sum[i] - sum[i-1] ≤ 1
差分约束系统可以转化为图论中的最短路问题。我们将每个sum[i]看作图中的一个节点,不等式关系看作图中的边:
如果图中存在负权环,则说明约束系统无解。
cpp复制#include<iostream>
#include<queue>
using namespace std;
// 定义常量
const int MAXN = 1005;
const int INF = 0x7fffffff;
// 图结构
class Edge {
public:
int to, nx, data; // 目标节点,下一条边索引,边权值
Edge() {}
Edge(int t, int n, int d) : to(t), nx(n), data(d) {}
};
// 全局变量
int n, cnt, a0, a1, b0, b1, l0, l1;
int h[MAXN], d[MAXN], a[MAXN]; // 头指针,距离数组,入队次数
bool f[MAXN]; // 是否在队列中
Edge e[4005]; // 边数组
queue<int> q; // SPFA队列
// 添加边
inline void Add(int x, int y, int z) {
e[++cnt] = Edge(y, h[x], z);
h[x] = cnt;
}
cpp复制int main() {
// 输入参数
cin >> n >> a0 >> b0 >> l0 >> a1 >> b1 >> l1;
// 建立基本约束:0 ≤ sum[i] - sum[i-1] ≤ 1
for(int i = 1; i <= n; i++) {
Add(i, i-1, 0); // sum[i] - sum[i-1] ≥ 0
Add(i-1, i, 1); // sum[i] - sum[i-1] ≤ 1
}
Add(n+1, 0, 0); // 虚拟超级源点
// 建立L0约束
for(int i = l0; i <= n; i++) {
int j = i - l0;
Add(i, j, b0 - l0); // sum[i] - sum[j] ≤ b0 - l0
Add(j, i, l0 - a0); // sum[j] - sum[i] ≤ l0 - a0
}
// 建立L1约束
for(int i = l1; i <= n; i++) {
int j = i - l1;
Add(i, j, -a1); // sum[i] - sum[j] ≥ a1 → sum[j] - sum[i] ≤ -a1
Add(j, i, b1); // sum[i] - sum[j] ≤ b1
}
// SPFA算法初始化
q.push(0); f[0] = true;
for(int i = 1; i <= n; ++i) d[i] = INF;
// SPFA主循环
while(!q.empty()) {
int x = q.front(); q.pop(); f[x] = false;
for(int i = h[x]; i; i = e[i].nx) {
int to = e[i].to;
if(d[x] + e[i].data < d[to]) {
d[to] = d[x] + e[i].data;
if(++a[to] == n) { // 负环检测
cout << -1;
return 0;
}
if(!f[to]) {
f[to] = true;
q.push(to);
}
}
}
}
// 输出结果
cout << d[n];
return 0;
}
边的关系建立:
SPFA算法:
虚拟源点:
SPFA算法在最坏情况下时间复杂度为O(VE),其中V是节点数(n+1),E是边数。本题中:
边界条件处理:
负环检测:
初始化问题:
约束转换错误:
使用静态数组:
队列优化:
输入优化:
除了差分约束系统,这道题还可以考虑以下方法:
动态规划:
网络流:
多约束条件:
更大数据范围:
输出具体方案:
这类差分约束问题在实际中有广泛应用,例如:
任务调度:
路径规划:
资源分配:
在解决这类问题时,关键是要:
这道NOI题目很好地考察了选手对差分约束系统的理解和实现能力,是图论与约束满足问题的经典结合。通过这道题,我们可以深入理解如何将实际问题抽象为图论模型,并运用算法高效求解。