P1060 云止聆佳响 官方题解
【部分分:测试点 1∼31\sim 31∼3】
- 当 时,可以枚举所有非空听音台集合。
- 判断这些听音台能否按照祖先关系排成一条龙吟,再统计相邻音高方向改变的次数与鸣响值。
- 枚举集合并检查,时间复杂度为 。
【部分分:测试点 4∼74\sim 74∼7】
- 设 表示以 结尾、发生 次激荡,最后一段分别上扬或低回的最大鸣响值。
- 枚举 的所有祖先 进行转移。若 ,可以接到上扬状态,否则可以接到低回状态。
- 每对祖先与后代至多处理 个状态,总时间复杂度为 。
【部分分:测试点 8∼118\sim 118∼11】
- 当 时,音高方向一旦确定就不能改变。
- 深度优先搜索时,分别维护各音高下上扬龙吟与低回龙吟的最大鸣响值。
- 查询严格更低或严格更高音高中的最大值即可完成转移。
【部分分:测试点 12∼1512\sim 1512∼15】
- 特殊性质 A 保证整棵树是一条链,祖先集合就是当前位置之前的前缀。
- 对每个激荡次数和末段方向维护一棵按音高建立的线段树。
- 查询严格更低或严格更高音高的前缀、后缀最大值,时间复杂度为 。
【部分分:测试点 16∼2016\sim 2016∼20】
- 在一般树上进行深度优先搜索。进入结点 时,数据结构中只保留根到 的祖先状态。
- 离开 时撤销它对数据结构的修改。这样同一套链上转移可以直接用于一般树。
- 音高先离散化。每个激荡次数与末段方向各维护一棵线段树。
【正解】
-
令 表示以 结尾、发生 次激荡且最后一段上扬的最大鸣响值。
-
令 表示以 结尾、发生 次激荡且最后一段低回的最大鸣响值。
-
单独选择 的状态记为 。不存在相邻听音台,因此尚未确定末段方向。
-
记 表示在当前祖先链上对所有满足 的状态取最大值,则
-
前两项分别表示从单点开始与继续上扬,第三项表示由低回转为上扬并新增一次激荡。
-
对称地,记 表示在当前祖先链上对所有满足 的状态取最大值,则
-
音高必须严格不同,因此查询区间不包含 对应的位置。
-
进入结点前保存各线段树在音高 处的旧值,写入由 得到的新状态。
-
递归处理完子树后恢复旧值,从而保证兄弟子树之间不会互相转移。
-
状态数为 。时间复杂度为 ,空间复杂度为 。
【参考代码】
#include <cstdio>#include <vector>#include <algorithm>#define MAXK 8using namespace std; const long long INF=-(1LL<<60); class SegmentTree{public: int n; vector<long long> Val; void Init(int m){ n=1; while(n<m) n<<=1; Val.assign(n<<1,INF); } long long Get(int p){return Val[n+p-1];} void Give(int p,long long v){ int x=n+p-1; Val[x]=v; for(x>>=1;x;x>>=1) Val[x]=max(Val[x<<1],Val[x<<1|1]); } long long Query(int l,int r){ if(l>r) return INF; long long Ret=INF; for(l=n+l-1,r=n+r-1;l<=r;l>>=1,r>>=1){ if(l&1) Ret=max(Ret,Val[l++]); if(!(r&1)) Ret=max(Ret,Val[r--]); } return Ret; }}; int main(){ int c,T; scanf("%d%d",&c,&T); while(T-->0){ int n,k; scanf("%d%d",&n,&k); vector<vector<int>> Son(n+1); for(int u=2,p;u<=n;u++) scanf("%d",&p),Son[p].push_back(u); vector<int> a(n+1),w(n+1),Dis; for(int u=1;u<=n;u++) scanf("%d",&a[u]),Dis.push_back(a[u]); for(int u=1;u<=n;u++) scanf("%d",&w[u]); sort(Dis.begin(),Dis.end()); Dis.erase(unique(Dis.begin(),Dis.end()),Dis.end()); int m=Dis.size(),State=1+2*(k+1); vector<SegmentTree> Tree(State); for(int i=0;i<State;i++) Tree[i].Init(m); vector<vector<long long>> Old(n+1,vector<long long>(State)); vector<pair<int,bool>> S; S.push_back({1,0}); long long Ans=0; while(!S.empty()){ int u=S.back().first; bool Exit=S.back().second; S.pop_back(); int p=lower_bound(Dis.begin(),Dis.end(),a[u])-Dis.begin()+1; if(Exit){ for(int s=0;s<State;s++) Tree[s].Give(p,Old[u][s]); continue; } vector<long long> Dp(State,INF); Dp[0]=w[u]; for(int j=0;j<=k;j++){ int Up=1+(j<<1),Down=Up+1; long long Best=max(Tree[0].Query(1,p-1),Tree[Up].Query(1,p-1)); if(j) Best=max(Best,Tree[1+((j-1)<<1)+1].Query(1,p-1)); if(Best>INF/2) Dp[Up]=Best+w[u]; Best=max(Tree[0].Query(p+1,m),Tree[Down].Query(p+1,m)); if(j) Best=max(Best,Tree[1+((j-1)<<1)].Query(p+1,m)); if(Best>INF/2) Dp[Down]=Best+w[u]; } for(int s=0;s<State;s++){ Old[u][s]=Tree[s].Get(p); Tree[s].Give(p,max(Old[u][s],Dp[s])); Ans=max(Ans,Dp[s]); } S.push_back({u,1}); for(int i=(int)Son[u].size()-1;i>=0;i--) S.push_back({Son[u][i],0}); } printf("%lld\n",Ans); } return 0;}