作业:
C题:
免费套餐:
题目类似一座树塔,在0-10范围内,某一秒会在若干个位置有馅饼,然后我们要求能接住的最多的馅饼。
首先我们构建一个二维数组,存储某个时间,某个位置的馅饼数目。**dp[t][x]**表示在t时刻,x位置有dp[t][x]个馅饼。
然后总结状态方程:
dp[t][x]=max(dp[t-1][x],dp[t-1][x-1],dp[t-1][x+1])+dp[t][x]
然后在写的时候发现:他不对,因为你初始时在5位置,从t=1开始,无法判断他的来源是否合法,也就是可能会出现t=1时刻,x位置属于0-3或者7-10。但是根据规则,一秒只能移动一个单位长度,所以不合法。
于是倒着看,从最后一个时刻往前数,在中间经过n个状态之后,最后一个时刻可能在任意位置。而遍历到开始的时刻的时候,由于初始化的DP矩阵中其他位置都是0,所以不影响判断,只需要输出dp[0][5]即可。
相应的状态转移方程如下:
dp[i][j]+=max(max(dp[i+1][j],dp[i+1][j-1]),dp[i+1][j+1]);
代码:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int m;
int dp[100010][12];
int main(){
int x,t;
while(cin>>m)
{
memset(dp,0,sizeof(dp));
if(m==0)break;
int maxt=0;
for(int i=0;i<m;i++)
{
cin>>x>>t;
dp[t][x]++;
maxt=max(maxt,t);
}
//dp[t][x]=max(dp[t-1][x],dp[t-1][x-1],dp[t-1][x+1])+dp[t][x]
dp[0][5]=0;
for(int i=maxt-1;i>=0;i--)
{
dp[i][0]+=max(dp[i+1][0],dp[i+1][1]);
for(int j=1;j<=9;j++)
{
dp[i][j]+=max(max(dp[i+1][j],dp[i+1][j-1]),dp[i+1][j+1]);
}
dp[i][10]+=max(dp[i+1][9],dp[i+1][10]);
}
cout<<dp[0][5]<<endl;
}
return 0;
}
D题:
经典的题目:没有上司的舞会。这是一道树形DP,因为输入的是一棵树的边。开始我们用前向星构建一棵树,然后根据出度和入度找出他的根节点,并且给叶子节点赋值。
令f[i][0/1]为以i节点为根的能得到的最大快乐值。第二维表示i这个点算不算进去。
于是得到递归表达式:
f[i][0]=max( f[x][1],f[x][0]);//上司不参加,下属可以参加也可以不参加
f[i][1]=w[i]+f[x][0];//上司参加,下属只能不参加
于是带入DFS进行递归处理即可。
代码:
#include <iostream>
using namespace std;
#define M 6005
struct node
{
int nxt,to,val;
}edge[M*2];
int head[M*2];int cnt;
void add(int x,int y)
{
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
head[x]=cnt;
}
int w[M],ru[M],chu[M];
int rt;
int dp[M][M];
//以u为根节点的子树 最大是多少 fa记录他爹
void dfs(int u,int fa)
{
dp[u][1]=w[u];
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==fa)continue;
dfs(v,u);
dp[u][1]+=dp[v][0];
dp[u][0]+=max(dp[v][1],dp[v][0]);
}
}
int main()
{
int n,u,v;
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<n;i++)
cin>>u>>v,add(u,v),add(v,u),chu[u]++,ru[v]++;
cin>>u>>v;
for(int i=1;i<=n;i++)
{
if(chu[i]==0)rt=i;
if(ru[i]==0)dp[i][1]=w[i];
}
dfs(rt,0);
cout<<max(dp[rt][0],dp[rt][1])<<endl;
return 0;
}
E题
Max Sum of Max-K-sub-sequence:
单调队列处理的题目,类似之前做的滑动窗口。这道题是一个环,我们在这个环上,找出一个连续的不超过K长度的序列,使这个序列的每个元素之和最大。
首先定义一个双端队列deque,作为单调队列使用。我们为了表示序列的和,降低其复杂度,使用前缀和表示区间和。即构造前缀和,使得S[i]-S[j]=a[j+1]+a[j+2]+…+a[i-1]+a[i].
按照题意:我们求要求出 [i-K,i] 的区间上的最小的那个前缀和s[j]使得S[i]-S[j]最大,即令区间 [j+1,i] 内的各个元素的和最大。为了解决环的问题,我们将数组扩大两倍。N个元素,区间为K我们只需要遍历到N+K-1即可包含环中的所有情况。
代码:
#include<bits/stdc++.h>
#include<stdio.h>
#include<deque>
#define inf 0x3f3f3f3f
using namespace std;
int T,N,K;
long long int b[2*100010],sum[2*100010];
int main(){
cin>>T;
while(T--)
{
deque<int>q;q.clear();
int l,r;
long long int ans=-inf;
memset(b,0,sizeof(b));memset(sum,0,sizeof(sum));
cin>>N>>K;
for(int i=1;i<=N;i++){scanf("%lld",&b[i]);b[N+i]=b[i];}
for(int i=1;i<=2*N;i++)sum[i]=sum[i-1]+b[i];
for(int i=1;i<=N+K-1;i++)
{
while(!q.empty()&&sum[q.back()]>sum[i-1])
q.pop_back();
q.push_back(i-1);//!!!!!!前缀和相减 sum[j]-sum[i-1]
while(!q.empty()&&q.front()+K<i)
q.pop_front();
if(ans<sum[i]-sum[q.front()]){l=q.front()+1;r=i;}
ans=max(ans,sum[i]-sum[q.front()]);
}
printf("%lld %d %d\n",ans,l>N?l-N:l,r>N?r-N:r);
}
return 0;
}