在大家的三连助攻下,TT 一举获得了超级多的猫咪,因此决定开一间猫咖,将快乐与大家一同分享。并且在开业的那一天,为了纪念这个日子,TT 在猫咖门口种了一棵苹果树。
一年后,苹果熟了,到了该摘苹果的日子了。
已知树上共有 N 个节点,每个节点对应一个快乐值为 w[i] 的苹果,为了可持续发展,TT 要求摘了某个苹果后,不能摘它父节点处的苹果。
TT 想要令快乐值总和尽可能地大,你们能帮帮他吗?
此题与“没有上司的舞会”极为相似,利用树型dp求解。
设状态f[i][0]为以i为子树且未选择i节点,f[i][1]为以i为子树且选择了i节点。转移方程为
其中求和对象是i节点的所有子节点。
实际编程过程中还要对树结构进行处理,得到正确的dp顺序。采用的方法是层次遍历整棵树,以遍历结果的逆序来动归。
#include
#include
#include
using namespace std;
struct node;
struct link{
link* next;
int id;
};
struct node{
int w;
link* child;
int pa;
void add(int v){
link* p=new link;
p->id=v;
p->next=child;
child=p;
}
void clear(){
link*p,*q;
p=child;
while(p!=NULL){
q=p;
p=p->next;
delete q;
}
child=NULL;
pa=-1;
}
};
node apple[6005];
int f[6005][3];
int N;
struct unit{
int level;
int id;
unit(int l,int i){
level=l;
id=i;
}
};
vector<unit> V;
queue<int> Q;
void scanTree(){
V.clear();
while(!Q.empty())Q.pop();
int p=1;
while(apple[p].pa!=-1)p=apple[p].pa;
int thisL,nextL,cotL;
thisL=nextL=cotL=0;
Q.push(p);
thisL++;
while(!Q.empty()){
int thisNode=Q.front();
Q.pop();
thisL--;
link* c=apple[thisNode].child;
while(c!=NULL){
Q.push(c->id);
nextL++;
c=c->next;
}
unit B(cotL,thisNode);
V.push_back(B);
if(thisL==0){
thisL=nextL;
nextL=0;
cotL++;
}
}
}
int main(){
while(cin>>N){
if(N==0)break;
for(int i=1;i<=N;i++){
apple[i].clear();
cin>>apple[i].w;
}
int L,K;
for(int i=1;i<N;i++){
cin>>L>>K;
apple[K].add(L);
apple[L].pa=K;
}
scanTree();
int k=N-1;
for(;k>=0;k--){
if(V[k].level!=V[k+1].level)break;
f[V[k].id][0]=0;
f[V[k].id][1]=apple[k].w;
}
for(;k>=0;k--){
link* c=apple[V[k].id].child;
int temp1=0;
int temp2=0;
while(c!=NULL){
temp1+=max(f[c->id][0],f[c->id][1]);
temp2+=f[c->id][0];
c=c->next;
}
f[V[k].id][0]=temp1;
f[V[k].id][1]=apple[V[k].id].w+temp2;
}
int ans=max(f[V[0].id][0],f[V[0].id][1]);
cout<<ans<<endl;
}
}
TT 猫咖的生意越来越红火,人越来越多,也越来越拥挤。
为了解决这个问题,TT 决定扩大营业规模,但猫从哪里来呢?
TT 第一时间想到了神秘人,想要再次通过完成任务的方式获得猫咪。
而这一次,神秘人决定加大难度。
给定一个环,A[1], A[2], A[3], … , A[n],其中 A[1] 的左边是 A[n]。要求从环上找出一段长度不超过 K 的连续序列,使其和最大。
这一次,TT 陷入了沉思,他需要你们的帮助。
本题使用到了单调队列dp。
题中对答案有如下要求:如果有多个结果,输出起始位置最小的,如果还是有多组结果,输出长度最短的。因此,要对课件中的最大连续字段和问题的解法进行一定的改进。
定义状态f[i]
为以i为开头的最大连续字段和,sum[i]
为后缀和,f[i]依然为sum[i]-min{sum[k]},但k取值范围为[i+1,i+1+K]。利用滑动窗口降低时间复杂度,将min{sum[k]}所指向的k储存在数组minS[]
中,转移方程转化为f[i]=sum[i]-sum[minS[i+1]]
。
扫描数组f,取最大值、对应元素索引与对应minS即可。
//#include
#include
#include
#include
#include
using namespace std;
int A[200005];
int sum[200005];
int minS[200005];
int f[200005];
int T,N,K;
int que[200005];
struct queue{
int p;
int q;
queue(){p=0;q=0;}
void reset(){
p=q=0;
}
bool empty(){return p==q;}
int size(){return q-p;}
void push(int v){que[q++]=v;}
int front(){return que[p];}
void pop(){p++;}//前出
int last(){return que[q-1];}
void erase(){q--;}//后出
};
queue Q;
void createMin(){
Q.p=Q.q=0;
Q.push(1);
for(int i=2;i<=K;i++){
while(!Q.empty()){
if(sum[Q.last()]>sum[i]) Q.erase();
else break;
}
Q.push(i);
}
minS[1]=Q.front();
for(int i=K+1;i<=2*N;i++){
if(i-Q.front()>=K) Q.pop();
while(!Q.empty()){
if(sum[Q.last()]>sum[i]) Q.erase();
else break;
}
Q.push(i);
minS[i-K+1]=Q.front();
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d",&N,&K);
for(int i=1;i<=N;i++){
scanf("%d",&A[i]);
A[i+N]=A[i];
}
int S=0;
for(int i=2*N;i>0;i--){
S+=A[i];
sum[i]=S;
}
createMin();
for(int i=1;i<=N;i++)
f[i]=sum[i]-sum[minS[i+1]];
int ans,tagL,tagR,temp;
ans=f[1];
if(minS[2]-1>N)temp=minS[2]-1-N;
else temp=minS[2]-1;
tagL=1;
tagR=temp;
for(int i=2;i<=N;i++){
if(f[i]>ans){
ans=f[i];
if(minS[i+1]-1>N)temp=minS[i+1]-1-N;
else temp=minS[i+1]-1;
tagL=i;
tagR=temp;
}else if(f[i]==ans){
if(minS[i+1]-1>N)temp=minS[i+1]-1-N;
else temp=minS[i+1]-1;
if(i<tagL){
tagL=i;
tagR=temp;
}else if(i==tagL){
if(temp<tagR)tagR=temp;
}
}
}
printf("%d %d %d\n",ans,tagL,tagR);
}
}