P1569 [USACO ?] Generic Cow Protests【来源请求】

记录93

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
long long dp[N],a[N],s[N];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		s[i]=s[i-1]+a[i];//前缀和
		dp[i]=-1;//前i头牛最多能分成多少个合法小组,-1为不可能
	} 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			if(s[i]-s[j-1]>=0&&dp[j-1]!=-1) dp[i]=max(dp[i],dp[j-1]+1);//前缀和的差就是第j到第i头牛的和
		}//-1作用,如果前面都分不了,那加上这一段也没用
	}//j-1头牛分成dp[j-1]组,把j到i作为新的一组更新值
	if(dp[n]<0) cout<<"Impossible";
	else cout<<dp[n];
    return 0;
}

题目传送门https://www.luogu.com.cn/problem/P1569


突破口

约翰家的 n 头奶牛聚集在一起,排成一列,正在进行一项抗议活动。第 i 头奶牛的理智度为 ai​。

约翰希望奶牛在抗议时保持理性,为此,他打算将所有的奶牛隔离成若干个小组,每个小组内的奶牛的理智度总和都要不小于零。

由于奶牛是按直线排列的,所以一个小组内的奶牛位置必须是连续的。请帮助约翰计算一下,最多分成几组。


思路

题目核心理解

🎯 目标

排成一列的 n 头奶牛 分成 若干个连续小组,使得:

  • 每个小组的 理智度总和 ≥ 0
  • 小组必须是 连续子数组
  • 要求 最多能分成多少组

✅ 这是一个 最大化分段数 的动态规划问题,带约束(每段和 ≥ 0)

❌ 无解情况

如果 整个序列无法被完全划分 (比如某部分无论如何都无法满足和 ≥ 0),则输出 "Impossible"


🧠 二、解题思路:动态规划 + 前缀和优化判断

关键观察

  • 因为小组必须连续,我们可以枚举 最后一段的起点 j(1 ≤ j ≤ i)
  • 若子段 [j, i] 的和 ≥ 0,且前 j−1 头牛能合法分组,则可更新 dp[i]

状态定义

  • dp[i]:表示 前 i 头奶牛 最多能分成多少个合法小组
  • 若无法合法分组,则 dp[i] = -1(作为"不可达"标记)

前缀和预处理

  • s[i] = a[1] + a[2] + ... + a[i]
  • 子段 [j, i] 的和 = s[i] - s[j-1]

状态转移

对每个 i(1 到 n):

cpp 复制代码
for j in 1..i:
    if (s[i] - s[j-1] >= 0) and (dp[j-1] != -1):
        dp[i] = max(dp[i], dp[j-1] + 1)

💡 解释:

  • dp[j-1] 是前 j−1 头牛的最大分组数
  • [j, i] 作为新的一组(需满足和 ≥ 0)
  • 总组数 = dp[j-1] + 1

初始条件

  • dp[0] = 0:0 头牛,0 组(隐含,代码中通过 s[0]=0dp[j-1] 当 j=1 时访问 dp[0] 实现)
  • 其他 dp[i] 初始化为 -1(不可达)

最终答案

  • dp[n] < 0 → 输出 "Impossible"
  • 否则输出 dp[n]

代码分析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
long long dp[N], a[N], s[N];
  • N=1010:因 n ≤ 1000,留余量
  • a[i]:第 i 头奶牛的理智度
  • s[i]:前缀和
  • dp[i]:前 i 头牛最多分组数(-1 表示不可能)
cpp 复制代码
int main(){
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        s[i] = s[i-1] + a[i]; // 计算前缀和
        dp[i] = -1;            // 初始化为不可达
    }
  • 读入数据,同时计算前缀和
  • s[0] = 0(全局变量默认初始化为 0)
  • dp[i] = -1 表示初始时前 i 头牛无法分组

⚠️ 注意:dp[0] 未显式赋值,但作为全局变量,dp[0] = 0(因为未初始化的全局 long long 为 0)

这正是我们需要的:前 0 头牛分 0 组


🔁 动态规划主循环

cpp 复制代码
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= i; j++){
  • 枚举当前考虑前 i 头牛
  • 枚举最后一段的起始位置 j(从 1 到 i)
cpp 复制代码
            if(s[i] - s[j-1] >= 0 && dp[j-1] != -1)
                dp[i] = max(dp[i], dp[j-1] + 1);
  • s[i] - s[j-1] = 第 j 到第 i 头牛的和
  • 若该和 ≥ 0,且前 j−1 头牛可分组(dp[j-1] != -1
  • 则尝试用 dp[j-1] + 1 更新 dp[i]

💡 举例:当 j=1 时,检查整个 [1,i] 是否 ≥0,且 dp[0]=0 合法 → 可分 1 组

cpp 复制代码
        }
    }

🔚 输出结果

cpp 复制代码
    if(dp[n] < 0) cout << "Impossible";
    else cout << dp[n];
    return 0;
}
  • dp[n] 仍为 -1 → 无法分组
  • 否则输出最大组数

🧪 样例验证:n=4, a = [2, 3, -3, 1]

前缀和 s = [0, 2, 5, 2, 3]

计算 dp

  • dp[0] = 0
  • i=1: j=1 → sum=2≥0, dp[0]=0 → dp[1]=1
  • i=2:
    • j=1: sum=5≥0 → dp[0]+1=1
    • j=2: sum=3≥0, dp[1]=1 → 1+1=2 → dp[2]=2
  • i=3:
    • j=1: sum=2≥0 → 0+1=1
    • j=2: sum=0≥0, dp[1]=1 → 2
    • j=3: sum=-3<0 → 跳过 → dp[3]=2
  • i=4:
    • j=1: sum=3≥0 → 1
    • j=2: sum=1≥0, dp[1]=1 → 2
    • j=3: sum=-2<0 → 跳过
    • j=4: sum=1≥0, dp[3]=2 → 3 → dp[4]=3 ✅

输出 3,符合样例!


⚠️ 复杂度与适用性

  • 时间复杂度:O(n²) → n=1000 时约 1e6 次操作,可接受
  • 空间复杂度:O(n)

💡 对于更大 n(如 1e5),需用单调队列优化,但本题 n≤1000,O(n²) 足够

总结

要点 说明
问题类型 最大化连续分段数,每段和 ≥ 0
DP 状态 dp[i] = 前 i 头牛最多分组数
转移关键 枚举最后一段起点 j,用前缀和快速判断合法性
边界处理 dp[0]=0 是合法起点
无解判断 dp[n] == -1
相关推荐
龙文浩_2 小时前
AI深度学习演进之路:从机器学习到大模型的范式变革
人工智能·深度学习·神经网络·算法·回归·线性回归
LTphy2 小时前
P3131 [USACO16JAN] Subsequences Summing to Sevens S
算法·前缀和·蓝桥杯
Albert Edison2 小时前
【ProtoBuf 语法详解】选项 option
开发语言·c++·序列化·反序列化·protobuf
繁星星繁2 小时前
Docker(一)
java·c语言·数据结构·c++·docker·容器·eureka
青稞社区.2 小时前
大模型RL算法梳理:从全量词元到部分词元的路径演化
算法
墨雪不会编程2 小时前
C++容器适配器【困难篇】双向队列详解
开发语言·c++
泡泡鱼(敲代码中)2 小时前
C++-string学习笔记
c语言·开发语言·c++·笔记·学习·visualstudio
qiqsevenqiqiqiqi2 小时前
一维dp知识点
算法·动态规划
ZHANG13HAO2 小时前
蚁群算法(蚁聚算法)深度解析与 mTSP 实战:物流多车协同配送优化
人工智能·算法·机器学习