该代码解决的是 图上的最短路径累积问题:给定一个有向图(或无向图,代码中未区分)和一系列必须按顺序访问的点,计算从序列起点到终点依次访问相邻点所走的最短路径总长度。
输入处理:
n
和访问序列长度 m
。m
的访问序列 v[]
(v[1]
是起点,v[m]
是终点)。n × n
邻接矩阵 dp[][]
,其中 dp[i][j]
表示点 i
到点 j
的直接距离(无边时需根据题目设定,代码中未显式处理无穷大)。计算所有点对的最短路径:
k, i, j
)动态更新最短路径:for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
dp[i][j]
存储点 i
到点 j
的全局最短路径长度。累加访问序列的路径长度:
v[]
,对每一对相邻点 (v[i-1], v[i])
,查询其最短路径并累加:ll ans = 0;
for (int i = 2; i <= m; i++) // 从序列的第2个点开始
ans += dp[v[i-1]][v[i]];
dist(v[1]→v[2]) + dist(v[2]→v[3]) + ... + dist(v[m-1]→v[m])
。输出结果:打印累加值 ans
。
n ≤ 100
),时间复杂度为 O(n³),空间复杂度 O(n²)。n
较大,可改用 Dijkstra 算法(但本题 n=100
适用 Floyd)。0
(dp[i][i]=0
),无边用较大值(如 0x3f3f3f3f
)表示,代码中未显式处理需注意输入数据。此方案高效利用了 Floyd 算法的全源最短路特性,将问题转化为简单的序列查询。
完整代码
#include
#define ll long long
using namespace std;
ll n,m,dp[105][105],v[10005];
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>v[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>dp[i][j];
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
ll ans=0;
for(int i=2;i<=m;i++)
ans+=dp[v[i-1]][v[i]];
cout<<ans;
return 0;
}
该代码解决的是 分组背包问题,结合了贪心排序思想。核心目标是在有限的士兵数量下,通过合理分配兵力到不同城堡,最大化总得分。得分规则为:在每个城堡中,若你的兵力严格大于某对手兵力的两倍,则战胜该对手得 1 分。
输入处理:
s
、城堡数 n
、士兵总数 m
。n × s
矩阵 arr
,其中 arr[j][i]
表示第 j
个城堡中第 i
个对手的兵力。for (int i = 1; i <= s; i++)
for (int j = 1; j <= n; j++)
cin >> arr[j][i]; // 城堡 j 的对手 i 的兵力
贪心预处理:
for (int i = 1; i <= n; i++)
sort(arr[i] + 1, arr[i] + 1 + s);
arr[i][k]
表示城堡 i
中第 k
小的对手兵力。此时若在城堡 i
派出 2 * arr[i][k] + 1
兵力,可至少战胜前 k
个对手(因为他们的兵力均 ≤ arr[i][k]
)。分组背包 DP:
dp[j]
表示使用 j
个士兵能获得的最大总得分。s
个物品:
k
:战胜前 k
个对手2 * arr[i][k] + 1
(所需最小兵力)k * i
(战胜 k
人 × 城堡编号 i
的得分系数)for (int i = 1; i <= n; i++) { // 枚举城堡(分组)
for (int j = m; j >= 0; j--) { // 倒序枚举士兵数
for (int k = 1; k <= s; k++) { // 枚举战胜对手数
int cost = 2 * arr[i][k] + 1;
if (j >= cost) {
dp[j] = max(dp[j], dp[j - cost] + k * i);
}
}
}
}
输出结果:
0
到 m
),取最大得分:ll ans = 0;
for (int i = 0; i <= m; i++)
ans = max(ans, dp[i]);
cout << ans;
贪心排序:
k
个对手的代价仅取决于第 k
小的兵力(2 * arr[i][k] + 1
)。分组背包模型:
k
人或零)。i
和战胜人数 k
。时间复杂度:O(n × m × s),满足题目约束(n, s ≤ 100,m ≤ 2e4)。
dp[0]=0
,但代码中默认全 0 已隐含此状态。此解法通过贪心预处理转化问题,再套用分组背包框架,高效解决了兵力分配问题。
完整代码
#include
#define ll long long
using namespace std;
ll s,n,m,dp[20005],arr[105][105];
int main()
{
cin>>s>>n>>m;
for(int i=1;i<=s;i++)
for(int j=1;j<=n;j++)
cin>>arr[j][i];
for(int i=1;i<=n;i++)
sort(arr[i]+1,arr[i]+1+s);
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=1;k<=s;k++){
if(j-2*arr[i][k]-1>=0)
dp[j]=max(dp[j],dp[j-2*arr[i][k]-1]+k*i);
}
}
}
ll ans=0;
for(int i=1;i<=m;i++)
ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
该代码解决的是 多重背包与特殊物品组合优化问题。问题分为两部分:处理普通物品(多重背包)和处理特殊物品(具有非线性收益的背包问题)。代码核心是动态规划,结合了二进制优化和启发式优化策略。
输入处理:
n
、特殊物品数 m
、背包容量 v
。a
、价值 b
和数量 c
。普通物品处理(二进制优化的多重背包):
int temp = 1;
while (temp < c) {
c -= temp;
for (int i = v; i >= temp * a; i--) {
dp[i] = max(dp[i], dp[i - temp * a] + temp * b);
}
temp *= 2;
}
// 处理剩余部分
for (int i = v; i >= c * a; i--) {
dp[i] = max(dp[i], dp[i - c * a] + c * b);
}
特殊物品处理(非线性收益优化):
g(j) = a*j*j + b*j + c
(j
是分配给该物品的容量)。i
,仅在关键点枚举 j
:
j = 0
和 j = i
。j0 = -b/(2a)
,取 j0
前后一定范围(如 200 个点)。vector<ll> new_dp = dp; // 保存上一状态
for (int i = 0; i <= v; i++) {
vector<int> candidates = {0, i}; // 端点
if (a != 0) {
double j0_val = -b / (2.0 * a);
int j0_floor = floor(j0_val), j0_ceil = ceil(j0_val);
// 极值点附近采样
for (int j = j0_floor - 200; j <= j0_floor + 200; j++)
if (j >= 0 && j <= i) candidates.push_back(j);
for (int j = j0_ceil - 200; j <= j0_ceil + 200; j++)
if (j >= 0 && j <= i) candidates.push_back(j);
}
// 去重并枚举候选点
sort(candidates.begin(), candidates.end());
auto last = unique(candidates.begin(), candidates.end());
candidates.erase(last, candidates.end());
for (int j : candidates) {
new_dp[i] = max(new_dp[i], dp[i - j] + a*j*j + b*j + c);
}
}
dp = new_dp; // 更新状态
输出结果:打印 dp[v]
(背包容量 v
下的最大收益)。
二进制优化:
特殊物品的启发式优化:
g(j)
的极值点在 j0 = -b/(2a)
附近。a = 0
时(线性函数),仅需枚举端点。该解法通过二进制优化处理普通物品,结合二次函数性质和关键点采样高效处理特殊物品,在较大规模数据下表现良好。
完整代码
#include
#define ll long long
using namespace std;
ll n,m,v,a,b,c,dp[10005];
int main()
{
cin>>n>>m>>v;
while(n--){
cin>>a>>b>>c;
int temp=1;
while(temp<c){
c-=temp;
for(int i=v;i>=temp*a;i--){
dp[i]=max(dp[i],dp[i-temp*a]+temp*b);
}
temp*=2;
}
for(int i=v;i>=c*a;i--){
dp[i]=max(dp[i],dp[i-c*a]+c*b);
}
}
while(m--){
cin>>a>>b>>c;
for(int i=v;i>=0;i--){
for(int j=0;j<=i;j++){
dp[i]=max(dp[i],dp[i-j]+a*j*j+b*j+c);
}
}
}
cout<<dp[v];
return 0;
}
该代码解决的是 货币支付问题:John 需要支付 m
元给商家,他有 n
种面值的硬币(数量有限),商家有无限量的硬币可以找零。目标是最小化 John 经手的总硬币数(支付硬币数 + 找回硬币数)。
关键结论:
P
满足:m ≤ P ≤ m + maxv - 1
(maxv
是最大面值,≤120)P
在 [m, m+120]
范围内多重背包(John 的支付方案):
dp[j]
表示 John 恰好支付 j
元所需的最小硬币数int temp = 1;
while (temp < c[i]) {
c[i] -= temp;
for (j = m+120; j >= temp*v[i]; j--)
dp[j] = min(dp[j], dp[j-temp*v[i]] + temp);
temp *= 2;
}
for (j = m+120; j >= c[i]*v[i]; j--)
dp[j] = min(dp[j], dp[j-c[i]*v[i]] + c[i]);
完全背包(商家的找零方案):
P
(m ≤ P ≤ m+120
):
sum = P - m
int arr[sum+1]; // arr[k] 表示找零 k 元的最小硬币数
arr[0] = 0;
for (int k = 1; k <= sum; k++) arr[k] = INF;
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= sum; j++)
arr[j] = min(arr[j], arr[j-v[i]] + 1);
更新答案:
dp[P]
和 arr[sum]
均有效:ans = min(ans, dp[P] + arr[sum]);
双重背包设计:
复杂度优化:
[m, m+maxv^2]
缩小到 [m, m+120]
关键实现细节:
dp[0]=0
,其他为极大值(11005)P ≥ m + maxv
,可通过减少一枚面值 ≤ maxv 的硬币使总硬币数减少,矛盾sum=0
(不需找零)时,arr[0]=0
保证正确性该解法高效结合了背包问题的优化技巧,充分利用了问题特性,在较大数据规模下仍保持良好性能。
完整代码
#include
#define ll long long
using namespace std;
int n,m,dp[11005],v[105],c[105],ans=11005;
bool flag=false;
void suan(int sum){
int arr[sum+1];
arr[0]=0;
for(int i=1;i<=sum;i++)
arr[i]=11005;
for(int i=1;i<=n;i++)
for(int j=v[i];j<=sum;j++)
arr[j]=min(arr[j],arr[j-v[i]]+1);
if(arr[sum]!=11005){
flag=true;
ans=min(ans,arr[sum]+dp[sum+m]);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i];
for(int i=1;i<=n;i++)
cin>>c[i];
for(int i=1;i<=m+120;i++)
dp[i]=11005;
for(int i=1;i<=n;i++){
int temp=1;
while(c[i]>temp){
c[i]-=temp;
for(int j=m+120;j>=temp*v[i];j--)
dp[j]=min(dp[j],dp[j-temp*v[i]]+temp);
temp*=2;
}
for(int j=m+120;j>=c[i]*v[i];j--)
dp[j]=min(dp[j],dp[j-c[i]*v[i]]+c[i]);
}
for(int i=m;i<=m+120;i++){
if(dp[i]<ans){
suan(i-m);
}
}
if(flag) cout<<ans;
else cout<<"-1";
return 0;
}
该代码解决的是 多限制条件下的物品选择问题:从 n
种物品中选择若干种,每种物品有三个属性 a, b, c
。要求选出的物品满足:
a
属性之和 ≥ m
b
属性之和 ≥ r
j
j
的前提下,最小化 c
属性之和状态定义:
dp[j][k][p]
表示选择 j
种物品时,a
属性之和为 k
,b
属性之和为 p
的最小 c
属性之和j ∈ [0, n]
, k ∈ [0, 100]
, p ∈ [0, 100]
(隐含要求 m, r ≤ 100
)初始化:
dp[0][0][0] = 0
(未选择任何物品)0x3f3f3f3f
动态规划(分组背包):
i
进行逆序枚举:
j
:从 i
递减到 1a
属性之和 k
:从 m
递减到 a[i]
b
属性之和 p
:从 r
递减到 b[i]
dp[j][k][p] = min(dp[j][k][p], dp[j-1][k-a[i]][p-b[i]] + c[i])
更新答案(存在缺陷):
dp[j][k][p]
有效(非无穷大):
j
大于历史最大种类数 now
,则更新 now = j
和 ans = dp[j][k][p]
j
等于 now
,则更新 ans = min(ans, dp[j][k][p])
k ≥ m
和 p ≥ r
输出结果:
flag = true
),输出最小 c
和 ans
-1
三维状态设计:
j
a
属性累积和 k
b
属性累积和 p
逆序枚举:
j, k, p
实现分组背包,避免物品重复选择局限性:
[101][101][101]
,隐含要求 m, r ≤ 100
k ≥ m
和 p ≥ r
,可能导致无效解被更新约束条件检查:
if (dp[j][k][p] != INF && k >= m && p >= r) {
// 更新 now 和 ans
}
最终扫描优化:
int now = 0, ans = INF;
for (int j = 0; j <= n; j++) {
for (int k = m; k <= 100; k++) {
for (int p = r; p <= 100; p++) {
if (dp[j][k][p] < INF) {
if (j > now) {
now = j;
ans = dp[j][k][p];
} else if (j == now) {
ans = min(ans, dp[j][k][p]);
}
}
}
}
}
大范围数据支持:
m, r > 100
,需修改状态设计:
k = min(k, m)
)n, m, r ≤ 100
时可行,更大数据需优化注意:此解法针对原题小数据范围设计,若
m, r > 100
需使用其他算法(如基于物品数量的分层 DP + 状态压缩)
完整代码
#include
#define ll long long
using namespace std;
int dp[101][101][101],n,a[101],b[101],c[101],m,r,now=0,ans=0;
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i]>>c[i];
}
cin>>m>>r;
memset(dp,0x3f,sizeof(dp));
dp[0][0][0]=0;
for(int i=1;i<=n;i++){
for(int j=i;j>=1;j--){
for(int k=m;k>=a[i];k--){
for(int p=r;p>=b[i];p--){
dp[j][k][p]=min(dp[j][k][p],dp[j-1][k-a[i]][p-b[i]]+c[i]);
if(dp[j][k][p]!=0x3f3f3f3f){
if(j==now){
ans=min(ans,dp[j][k][p]);
}
else if(j>now){
now=j;
ans=dp[j][k][p];
}
}
}
}
}
}
cout<<ans;
return 0;
}
该代码解决的是 双阶段任务调度问题:有 n
头牛需要依次完成上山和下山任务,每头牛的上山时间为 up
,下山时间为 down
。关键约束是:当一头牛开始下山时,下一头牛可以同时开始上山(即上山与下山阶段可以重叠)。目标是最小化所有牛完成任务的总时间。
贪心排序策略:
up ≤ down
(上山快下山慢)up > down
(上山慢下山快)up
升序排列(上山时间短的优先)down
降序排列(下山时间长的优先)bool cmp(point &s1, point &s2) {
if (s1.up < s1.down) { // s1是第一类
if (s2.up < s2.down) // s2是第一类
return s1.up < s2.up; // 按up升序
return true; // 第一类排第二类前
} else { // s1是第二类
if (s2.up > s2.down) // s2是第二类
return s1.down > s2.down; // 按down降序
return false; // 第二类排第一类后
}
}
时序模拟:
uptime = 0
(累计上山时间)lastend = 0
(最后一头牛下山结束时间)for (int i = 1; i <= n; i++) {
uptime += arr[i].up; // 累加上山时间
// 更新下山结束时间:
// - 下山开始时间 = max(上一头牛的下山结束时间, 当前牛上山结束时间)
// - 下山结束时间 = 下山开始时间 + 下山耗时
lastend = max(lastend, uptime) + arr[i].down;
}
lastend
即最小总时间第一类牛(上山快)优先处理:
第二类牛(下山慢)前置处理:
重叠利用关键:
lastend = max(lastend, uptime) + down
精确建模了任务重叠:
lastend > uptime
:下山连续进行(无空闲)uptime > lastend
:上山结束后立即开始下山假设两头牛:
up=1
, down=100
(第一类)up=100
, down=1
(第二类)按贪心排序:牛A → 牛B
uptime=1
, lastend = max(0,1)+100=101
uptime=1+100=101
, lastend = max(101,101)+1=102
若逆序(牛B → 牛A):
uptime=100
, lastend=100+1=101
uptime=100+1=101
, lastend = max(101,101)+100=201
(更差)关键洞察:通过合理排序,使耗时长的下山阶段尽可能与后续上山阶段重叠,最大化利用并行性。
完整代码
#include
#define ll long long
using namespace std;
struct point{
int up,down;
}arr[50005];
int n;
bool cmp(point &s1,point &s2){
if(s1.up<s1.down){
if(s2.up<s2.down){
return s1.up<s2.up;
}
return true;
}
else{
if(s2.up>s2.down){
return s1.down>s2.down;
}
return false;
}
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i].up>>arr[i].down;
}
sort(arr+1,arr+1+n,cmp);
int uptime=0,lastend=0;
for(int i=1;i<=n;i++){
uptime+=arr[i].up;
lastend=max(lastend,uptime)+arr[i].down;
}
cout<<lastend;
return 0;
}
该代码解决的是 区间修改与三角函数和查询问题:维护一个整数序列,支持两种操作:
[l, r]
的所有数加 k
[l, r]
所有数的正弦值之和(结果四舍五入保留一位小数)数据结构设计:
l, r
:节点覆盖的区间lz
:区间加法懒标记(累计未下传的增量)sums
:区间内所有数的正弦值之和sumc
:区间内所有数的余弦值之和关键数学原理(和角公式):
a
增加 k
时:
sin(a+k) = sin(a)cos(k) + cos(a)sin(k)
cos(a+k) = cos(a)cos(k) - sin(a)sin(k)
k
:
cos(k)
+ 原余弦和 × sin(k)
cos(k)
- 原正弦和 × sin(k)
核心操作:
sums
和 sumc
sums
和 sumc
,累加懒标记sums
结果处理:
round(result * 10) / 10
高效区间维护:
数学性质应用:
精度处理:
懒标记优化:
sin(k)
和 cos(k)
避免重复计算查询优化:
精度增强:
long double
)减少累积误差关键洞察:将整数加法转化为三角函数的线性变换,利用线段树高效维护区间和,通过和角公式保证更新的正确性。
完整代码
#include
#define ll long long
using namespace std;
struct node{
int r,l;
ll lz;
double sums,sumc;
}tree[820020];
int a,n,m;
double s[200005],c[200005];
void build(int i,int l,int r){//建树
tree[i].l=l,tree[i].r=r,tree[i].lz=0;
if(l==r){
tree[i].sums=s[l];
tree[i].sumc=c[l];
return;
}
int mid=(l+r)/2;
build(2*i,l,mid);//递归左右子树
build(2*i+1,mid+1,r);
tree[i].sums=tree[2*i].sums+tree[2*i+1].sums;//回溯更新sum
tree[i].sumc=tree[2*i].sumc+tree[2*i+1].sumc;
}
void push_down(int i){
if(tree[i].lz!=0){
ll v=tree[i].lz;
tree[2*i].lz+=v;
tree[2*i+1].lz+=v;
double ls1=tree[2*i].sums,lc1=tree[2*i].sumc,ls2=tree[2*i+1].sums,lc2=tree[2*i+1].sumc;
tree[2*i].sums=ls1*cos(v)+lc1*sin(v);
tree[2*i].sumc=lc1*cos(v)-ls1*sin(v);
tree[2*i+1].sums=ls2*cos(v)+lc2*sin(v);
tree[2*i+1].sumc=lc2*cos(v)-ls2*sin(v);
tree[i].lz=0;
}
}
void add(int i,int l,int r,ll k){
if(tree[i].l>=l&&tree[i].r<=r){
double ls=tree[i].sums,lc=tree[i].sumc;
tree[i].sums=ls*cos(k)+lc*sin(k);
tree[i].sumc=lc*cos(k)-ls*sin(k);
tree[i].lz+=k;
return;
}
push_down(i);//本节点已更新完毕,把懒标记传递给子树
if(tree[2*i].r>=l) add(i*2,l,r,k);
if(tree[2*i+1].l<=r) add(2*i+1,l,r,k);
tree[i].sums=tree[2*i].sums+tree[2*i+1].sums;//回溯更新sum
tree[i].sumc=tree[2*i].sumc+tree[2*i+1].sumc;
}
double search(int i,int l,int r){
if(tree[i].l>=l&&tree[i].r<=r) return tree[i].sums;
if(tree[i].l>r||tree[i].r<l) return 0;
push_down(i);
double ans=0;
if(tree[2*i].r>=l) ans+=search(2*i,l,r);
if(tree[2*i+1].l<=r) ans+=search(2*i+1,l,r);
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a);
s[i]=sin(a);
c[i]=cos(a);
}
build(1,1,n);
scanf("%d",&m);
ll x,y,k;
while(m--){
scanf("%lld%lld%lld",&a,&x,&y);
if(a==1){
scanf("%lld",&k);
add(1,x,y,k);
}
else printf("%.1f\n",round(search(1,x,y)*10)/10);
}
return 0;
}