C - TT 的奖励(必做)
解题思路:
对于这道题目来讲,感觉算是一道比较经典的dp问题,但是,自己一开始没有想到利用dp解决,在这一点上,说明自己对于dp的问题感触不够深。当看到这道题目时,首先出现在自己脑海中的是之前做过的两道题目——即宇宙射线问题与矩阵选数问题,总感觉其中有些类似的思维与处理办法,因为在我的印象中,在降低时间复杂度方面,增维与标记两种方式给我感触比较深。那么对于这道题目来讲,很显然,模仿宇宙射线的解法,对到达过的状态进行标记,在这里我们需要利用队列先进先出的性质,状态的入队与数值的更新或许是不同步的,但是是保证在出队之前更新为最优的,这个处理我记得在图论中也曾用到。这种做法能够应用于本题在于时间是逐步递增的,在前一秒的所有状态考虑完备之前后一秒的状态是不会进行考虑的。
而其实,这道题利用动态规划思想进行求解更优,这种甩锅式的处理在一定程度上简化了我们的思考时间,省时省力的做法是我们一直追求的,在这道题目上面,我们将状态定位两维的,第一维表示秒数,第二维表示在那个点,这样一个新的状态只能由上一秒的两个或三个状态更新而来,思维大大简化,代码长度也缩短了很多。
代码1:
#include<iostream>
#include<stdio.h>
#include<queue>
#include<string.h>
#include<cmath>
using namespace std;
int m,a,b;
int sec=0;
bool label[11][100001]={0};
int location[11][100001]={0};
int func[11][100001]={0};
queue<pair<int,int>> qu;
void judge()
{
while(qu.size())qu.pop();
qu.push(pair<int,int>(5,1));
qu.push(pair<int,int>(4,1));
qu.push(pair<int,int>(6,1));
label[5][1]=1;
label[4][1]=1;
label[6][1]=1;
func[5][1]=location[5][1];
func[4][1]=location[4][1];
func[6][1]=location[6][1];
while(qu.size())
{
int c=qu.front().first;
int d=qu.front().second;
qu.pop();
if(d>=sec)
break;
if(c<=9)
{
if(label[c+1][d+1]==0)
{
func[c+1][d+1]=func[c][d]+location[c+1][d+1];
label[c+1][d+1]=1;
qu.push(pair<int,int>(c+1,d+1));
}
else
{
func[c+1][d+1]=max(func[c+1][d+1],func[c][d]+location[c+1][d+1]);
}
}
if(c>=1)
{
if(label[c-1][d+1]==0)
{
func[c-1][d+1]=func[c][d]+location[c-1][d+1];
label[c-1][d+1]=1;
qu.push(pair<int,int>(c-1,d+1));
}
else
{
func[c-1][d+1]=max(func[c-1][d+1],func[c][d]+location[c-1][d+1]);
}
}
if(label[c][d+1]==0)
{
func[c][d+1]=func[c][d]+location[c][d+1];
label[c][d+1]=1;
qu.push(pair<int,int>(c,d+1));
}
else
{
func[c][d+1]=max(func[c][d+1],func[c][d]+location[c][d+1]);
}
}
int cc=0;
for(int i=0;i<=10;i++)
{
cc=max(cc,func[i][sec]);
}
cout<<cc<<endl;
}
int main()
{
scanf("%d",&m);
while(m!=0)
{
sec=0;
memset(label,0,sizeof(label));
memset(func,0,sizeof(func));
memset(location,0,sizeof(location));
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
location[a][b]++;
sec=max(sec,b);
}
judge();
scanf("%d",&m);
}
}在这里插入代码片
代码2:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int m;
int fun[100005][11]={0};
int dp[100005][11]={0};
int sec=0;
int a,b;
int main()
{
ios::sync_with_stdio(false);
while(cin>>m)
{
if(m==0)break;
sec=0;
memset(fun,0,sizeof(fun));
memset(dp,0,sizeof(dp));
for(int i=0;i<m;i++)
{
cin>>a>>b;
fun[b][a]++;
sec=max(sec,b);
}
dp[1][5]=fun[1][5];
dp[1][4]=fun[1][4];
dp[1][6]=fun[1][6];
for(int i=2;i<=sec;i++)
{
for(int j=0;j<=10;j++)
{
if(j>=1&&j<=9)
dp[i][j]=max(dp[i-1][j+1]+fun[i][j],max(dp[i-1][j-1]+fun[i][j],dp[i-1][j]+fun[i][j]));
else if(j==0)
dp[i][j]=max(dp[i-1][j+1]+fun[i][j],dp[i-1][j]+fun[i][j]);
else
dp[i][j]=max(dp[i-1][j-1]+fun[i][j],dp[i-1][j]+fun[i][j]);
}
}
int func=0;
for(int i=0;i<=10;i++)
{
func=max(func,dp[sec][i]);
}
cout<<func<<endl;
}
} 在这里插入代码片
D - TT 的苹果树(选做)
在大家的三连助攻下,TT 一举获得了超级多的猫咪,因此决定开一间猫咖,将快乐与大家一同分享。并且在开业的那一天,为了纪念这个日子,TT 在猫咖门口种了一棵苹果树。
一年后,苹果熟了,到了该摘苹果的日子了。
已知树上共有 N 个节点,每个节点对应一个快乐值为 w[i] 的苹果,为了可持续发展,TT 要求摘了某个苹果后,不能摘它父节点处的苹果。
TT 想要令快乐值总和尽可能地大,你们能帮帮他吗?
Input
结点按 1~N 编号。
第一行为 N (1 ≤ N ≤ 6000) ,代表结点个数。
接下来 N 行分别代表每个结点上苹果的快乐值 w[i](-128 ≤ w[i] ≤ 127)。
接下来 N-1 行,每行两个数 L K,代表 K 是 L 的一个父节点。
输入有多组,以 0 0 结束。
Output
每组数据输出一个整数,代表所选苹果快乐值总和的最大值。
输入样例
7
1
1
1
1
1
1
1
1 3
7 4
2 3
4 5
6 4
3 5
0 0
输出样例
5
解题思路:
这个题目的大致思路与思维过程助教已经在课堂进行过讲解,这属于一道树型dp的题目,关于树型dp的题目,其解决方案都比较巧妙,不过,这些都是建立在树这种特殊的结构上的,其性质比较多,层次比较分明,关于很多问题,都存在着绝对最优且省时省力的做法的。在这个方面,我们需要多积累。另外,关于本道题目,其自身还是基于dp的,但这里面有个增维的处理比较巧妙,大大简化了题目解决时的难度,(记不清那道题目了,反正之前一道题目也是这样处理的),我们反复强调过了这种思想的巧妙性与重要性,故我们要有非常深的感触才行。另外,是本题中对于递归函数参数的传递方面,以及过程处理方面的标记,这些应该是一些套路性的做法,我们需要完全掌握。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<algorithm>
using namespace std;
struct Edge{
int to;
int next;
}edge[6005];
int w[6005];
int head[6005];
int tot=0;
int deg[6005]={0};
int N;
int num[6005][2]={0};
void init()
{
tot=0;
memset(w,0,sizeof(w));
memset(deg,0,sizeof(deg));
memset(head,-1,sizeof(head));
memset(num,-1,sizeof(num));
}
void add(int begin,int end)
{
edge[tot].next=head[begin];
edge[tot].to=end;
head[begin]=tot++;
}
int func()
{
int fun;
for(int i=1;i<=N;i++)
{
if(deg[i]==0)
{
fun=i;
break;
}
}
return fun;
}
int ff(int root,int label)
{
if(num[root][label]!=-1)
{
return num[root][label];
}
if(head[root]==-1)
{
if(label==0)
{
return num[root][0]=0;
}
else
{
return num[root][1]=w[root];
}
}
else
{
int cc=0,dd=0;
for(int i=head[root];i!=-1;i=edge[i].next)
{
cc+=max(ff(edge[i].to,0),ff(edge[i].to,1));
dd+=ff(edge[i].to,0);
}
if(label==1)
{
return num[root][1]=w[root]+dd;
}
else
{
return num[root][0]=cc;
}
}
}
int main()
{
scanf("%d",&N);
while(N!=0)
{
init();
for(int i=1;i<=N;i++)
{
scanf("%d",&w[i]);
}
int a,b;
for(int i=1;i<N;i++)
{
scanf("%d%d",&a,&b);
add(b,a);
deg[a]=1;
}
int fun=func();
cout<<max(ff(fun,0),ff(fun,1))<<endl;
scanf("%d",&N);
}
} 在这里插入代码片
E - TT 的神秘任务3(选做)
TT 猫咖的生意越来越红火,人越来越多,也越来越拥挤。
为了解决这个问题,TT 决定扩大营业规模,但猫从哪里来呢?
TT 第一时间想到了神秘人,想要再次通过完成任务的方式获得猫咪。
而这一次,神秘人决定加大难度。
给定一个环,A[1], A[2], A[3], … , A[n],其中 A[1] 的左边是 A[n]。要求从环上找出一段长度不超过 K 的连续序列,使其和最大。
这一次,TT 陷入了沉思,他需要你们的帮助。
Input
第一行一个整数 T,表示数据组数,不超过 100。
每组数据第一行给定两个整数 N K。(1 ≤ N ≤ 100000, 1 ≤ K ≤ N)
接下来一行,给出 N 个整数。(-1000 ≤ A[i] ≤ 1000)。
Output
对于每一组数据,输出满足条件的最大连续和以及起始位置和终止位置。
如果有多个结果,输出起始位置最小的,如果还是有多组结果,输出长度最短的。
Sample Input
4
6 3
6 -1 2 -6 5 -5
6 4
6 -1 2 -6 5 -5
6 3
-1 2 -6 5 -5 6
6 6
-1 -1 -1 -1 -1 -1
Sample Output
7 1 3
7 1 3
7 6 2
-1 1 1
解题思路:
这是一道利用单调队列优化dp的题目,关于具体做法上课的时候助教做了一下解释,其比之前做过的题目求连续最大和多出了要限定在最长长度不超过k的情况下。当时,我们做那道题目的时候,存在两种做法,一种是利用dp求解,另一种是利用前缀和进行求解。对于这道题目,我们采用的正是单调队列控制下的前缀和求解。
其中,令我感触比较深的是前缀和的求解,这样的转化比较巧妙,再一次凸显了前缀和的强大求解能力,前缀和给我的较大感触就是将一些比较离散的问题变得系统化,前缀和维护的变量往往很少,拿最大和的例子来讲,其只需要维护开头与结尾两个点,理解方便,操作也方便。不过这道题目中,也体现出了静态的思想,这与dp中的思维是一样的,其主要体现在我们仍以第i个元素为某一段的最后元素进行求解,其实,这是前缀和解决这道题目的核心内容。在这道题目上,我们利用的是deque双端队列,这是一种自己以前尚未用过的结构,算是知识面上的一种扩展。最后需要注意的是,对于这种题目,形成一个比较好的思路已经不容易,我们一定注意边界问题的处理,边界问题对自己来讲总感觉很棘手,这需要多注意,可以去培养其中的思维方式。
#include<iostream>
#include<stdio.h>
#include<deque>
#include<string.h>
using namespace std;
int T;
int N,K;
int num[200005]={0};
deque<int> deq;
int sum[200005]={0};
int beg,en,ans;
int main()
{
scanf("%d",&T);
while(T--)
{
memset(num,0,sizeof(num));
memset(sum,0,sizeof(sum));
while(deq.size())deq.pop_back();
scanf("%d%d",&N,&K);
for(int i=1;i<2*N;i++)
{
if(i<=N)
{
scanf("%d",&num[i]);
}
else
num[i]=num[i-N];
}
sum[1]=num[1];
for(int i=2;i<2*N;i++)
{
sum[i]=sum[i-1]+num[i];
}
sum[0]=0;
ans=sum[0];
beg=0;
en=0;
deq.push_back(0);
for(int i=1;i<2*N;i++)
{
while(!deq.empty()&&sum[deq.back()]>sum[i])
deq.pop_back();
deq.push_back(i);
if(i-K>deq.front())
deq.pop_front();
if(ans<sum[deq.back()]-sum[deq.front()])
{
ans=sum[deq.back()]-sum[deq.front()];
beg=deq.front();
en=deq.back();
}
}
if(!(beg==0&&en==0))
{
cout<<ans<<" ";
cout<<beg+1<<" ";
if(en>N)
{
cout<<en-N<<endl;
}
else
{
cout<<en<<endl;
}
}
else
{
int funn=num[1],tt=1;
for(int i=2;i<=N;i++)
{
if(funn<num[i])
{
funn=num[i];
tt=i;
}
}
cout<<funn<<" "<<tt<<" "<<tt<<endl;
}
}
}在这里插入代码片