P1068 梦的七重回旋 官方题解
Gioush OJ · P1068 梦的七重回旋
【部分分:测试点 111】
- 每个片段有编入第一条梦轨、编入第二条梦轨和舍弃三种选择。直接枚举全部 种选择,检查两条梦轨之间是否存在正长度的重叠。
- 对每个合法方案统计两条梦轨的片段数量,既可以更新没有额外限制的答案,也可以更新方案中每个已选片段的强制答案。时间复杂度为 。
【性质观察】
- 只需要关心片段的开始与结束时刻。将全部端点离散化后,端点数量记为 ,有 。令 表示起止时刻都完整落在 中的片段数量。
- 扫描一组合法方案时,两条梦轨占用的时间可以切成若干首尾相接的时间段,每一段只交给其中一条梦轨。若一段已经交给某条梦轨,那么把所有完整落在这一段中的片段都加入该梦轨不会破坏合法性,也不会使平衡度变小。
【评分方式 A】
- 定义 表示只考虑时刻 ,第一条梦轨恰好得到 个片段时,第二条梦轨最多能够得到多少个片段。初值为 ,其余状态均为负无穷。
- 枚举最后一段时间 。将其中全部 个片段交给第二条梦轨,或者交给第一条梦轨,可以得到
- 为了处理强制包含片段的询问,再定义完全对称的后缀状态 :只考虑时刻 ,第一条梦轨恰好得到 个片段时,第二条梦轨最多能够得到多少个片段。
- 边界为 ,其余状态为负无穷,完整转移为
没有额外限制时的答案为 。
【部分分:测试点 2∼32\sim 32∼3】
- 评分方式 A 只要求没有额外限制时的答案。为了继续处理每个片段必须被选中的限制,我们固定一个时间段 ,将其中全部片段交给第一条梦轨。再设第一条梦轨在左侧和右侧分别得到 个片段,则两条梦轨最终能够得到的数量分别为
- 因此定义
枚举 可以在 的时间内求出全部 。
- 设第 个片段离散化后的端点为 。只要 且 ,第 个片段就完整落在 中,从而一定会被选入第一条梦轨。
- 所以第 个强制答案为
枚举包含它的时间段不会遗漏最优方案,因为在任意最优方案中,都可以取第一条梦轨覆盖该片段的连续时间段作为 。
【正解】
-
上一档枚举 的复杂度过高。计算 时,固定 。随着 增大, 单调增大,而 单调不增,因此两者的最小值只会先增大再减小,能够改进答案的 位于两者的交界附近。
-
当 增大时,第一项整体增大,而 单调不增,所以这条改进边界只会向更小的 移动。固定 后,令 从右端开始。按照 扫描,每次只枚举 ,记录本轮最后一次改进 的位置,再令 移到该位置。
-
可以在 的时间内直接预处理。前缀与后缀动态规划各有 个状态,每个状态枚举最后一段的端点,总时间复杂度为 。
-
使用双指针后,全部 的计算也是 。最后枚举包含每个片段的 仍为 。因此总时间复杂度为 ,空间复杂度为 。
-
端点相接不算正长度重叠,所以前后两段可以共用同一个离散化端点。所有不可达的动态规划状态必须初始化为负无穷。
【参考代码】
/*Author:EhundateghDate:2026/8/18Name:dream.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 410using namespace std; int c,T,n,Mapping[MAXN<<1],In1,In2,Ori[MAXN<<1],cnt1=0,cnt2=0,Ans1=0;int Count[MAXN][MAXN],Pre[MAXN][MAXN],Suff[MAXN][MAXN],Sum[MAXN][MAXN]; void Discrete() { sort(Ori+1,Ori+cnt1+1); for (int i=1;i<=cnt1;i++) { if (i==1||Ori[i]!=Ori[i-1]) Mapping[++cnt2]=Ori[i]; }} int Get(int x) { return lower_bound(Mapping+1,Mapping+cnt2+1,x)-Mapping;} struct seg{ int l,r;}S[MAXN]; void Solve() { cnt1=cnt2=Ans1=0; memset(Pre,0xcf,sizeof(Pre)); memset(Suff,0xcf,sizeof(Suff)); memset(Sum,0,sizeof(Sum)); memset(Count,0,sizeof(Count)); scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%d%d",&In1,&In2); S[i]={In1,In1+In2}; Ori[++cnt1]=In1;Ori[++cnt1]=In1+In2; } Discrete(); for (int i=1;i<=n;i++) {S[i].l=Get(S[i].l);S[i].r=Get(S[i].r);} for (int i=0;i<=cnt2+1;i++) { for (int j=0;j<=i;j++) { for (int k=1;k<=n;k++) { if (S[k].l>=j&&S[k].r<=i) Count[j][i]++; } } } Pre[0][0]=0; Suff[cnt2+1][0]=0; for (int i=1;i<=cnt2;i++) { for (int j=0;j<=n;j++) { for (int k=0;k<=i;k++) { Pre[i][j]=max(Pre[i][j],Pre[k][j]+Count[k][i]); if (j-Count[k][i]>=0) Pre[i][j]=max(Pre[i][j],Pre[k][j-Count[k][i]]); } } } for (int i=cnt2;i>=1;i--) { for (int j=0;j<=n;j++) { for (int k=cnt2+1;k>=i;k--) { Suff[i][j]=max(Suff[i][j],Suff[k][j]+Count[i][k]); if (j-Count[i][k]>=0) Suff[i][j]=max(Suff[i][j],Suff[k][j-Count[i][k]]); } } } for (int i=0;i<=n;i++) Ans1=max(Ans1,min(i,Suff[1][i])); printf("%d\n",Ans1); for (int r=1;r<=cnt2;r++) { for (int l=1;l<=r;l++) { int p=cnt2,my=0; for (int cx=0;cx<=n;cx++) { for (int cy=p;cy>=0;cy--) { if (Sum[l][r]<min(cy+cx+Count[l][r],Pre[l][cx]+Suff[r][cy])) { my=cy; Sum[l][r]=min(cy+cx+Count[l][r],Pre[l][cx]+Suff[r][cy]); } } p=my;my=0; } } } for (int i=1;i<=n;i++) { int Ans2=0; for (int j=1;j<=S[i].l;j++) { for (int k=S[i].r;k<=cnt2;k++) Ans2=max(Ans2,Sum[j][k]); } printf("%d\n",Ans2); }} int main() { scanf("%d%d",&c,&T); while (T-->0) Solve(); return 0;}