1871
自最后向前遍历,如果从最后往前断了,就false,不然就true
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n=s.size(),cur=n-1;
if(s[cur]!='0'){return false;}
for(int i=n-2;i>=0;i--){
if(s[i]=='0'){
if(cur-i<=maxJump&&cur-i>=minJump){
cur=i;
}else{
return false;
}
}
}
return true;
}
};

这样一次遍历不行,因为会有多条路径,这个cur只保存了一条路径,所以会漏掉一些情况从而误判
那还是bfs吧,最省事
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n=s.size();
if(s[n-1]!='0'){return false;}
vector<bool>flag(n,false);
queue<int>q;
q.push(0);
flag[0]=true;
while(!q.empty()){
int cur=q.front();
q.pop();
for(int i=cur+minJump;i<=min(cur+maxJump,n-1);i++){
if(s[i]=='0'&&!flag[i]){
if(i==n-1){return true;}
flag[i]=true;
q.push(i);
}
}
}
return flag[n-1];
}
};
这代码怎么还能超时?就是全是0的情况,
但bfs每个点都只遍历一次阿,不也就On级别的复杂度?怎么对于10^5级别的数据还能超时?

如果用DP,该怎么划分子问题才能无后效性?
dp[i]=dp[j]或的和,j为i所能跳转到的那些元素;
但如果不限制上界,又何来缩减问题规模一说?
那还是从后往前遍历,还是维护一个队列,来往前寻找吗?这样才可能有多条路径返回起点
维护一个滑动窗口,就是从左侧开始,表示能到达当前i的所有元素;那他应该是[i-maxJump,i-minJump];然后维护其中的0是否能被到达;当滑动窗口移动,即i向前移动时,对于右侧新增的值,如果是0,看他能否被达到,用一个flag数组来维护每个0能否被到达,如果能就把它的下标入窗口队列,不然不管;对于左侧,检测下标是否过时了,即Index<i-maxJump,如果过时了就移除;甚至应该不用窗口队列,就是维护这个窗口里所能到达的0的最大下标即可;如果这个最大下标出了窗口,即maxIndex<i-maxJump,那就直接return false;否则就一直到i为数组末尾,表示可达
我说的最大值,是说滑动窗口内部存在0且能被到达的下标的最大值index,它本身就一定是在窗口内部的,而且是不断被维护的最大值,所以当他小于i-maxJump时,就意味着后面的0一定都不可达;此外,如何完成滑动窗口的初始化,即我知道要维护[i-maxJump,

class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n=s.size(),maxIndex=0;;
if(s[n-1]!='0'){return false;}
//vector<bool>flag(n,false);//[i-maxJump,i-minJump]
for(int i=minJump;i<=maxJump;i++){
//flag[i]=s[i]=='0';
if(s[i]=='0'){
//flag[i]=true;
maxIndex=i;
}
}
for(int i=maxJump+1;i<n;i++){
cout<<"cur i:"<<i<<endl;
if(maxIndex<i-maxJump){return false;}
//if(maxIndex>i-minJump){return false;}
if(s[i]=='0'){maxIndex=i;}
}
return true;
}
};
这个确实就是太近所导致的
就是应该要保证这个维护的值,确实是该在窗口里
入队的位置是在i-minJump,出队的位置是在i-maxJump
我现在对于出队的处理,就是if(maxIndex<i-maxJump){return false;}阿,这个maxIndex就是指窗口当中那个最不可能被淘汰的计数,即cnt=1时的情况,如果中间的某个窗口存在cnt=0,那么后面的0不都到不了吗,而且能保证这个cnt是能被此时的i访问到的
就是说只保留最右侧满足条件的话,相当于只保留了一条路径,不过由于本身是I的滑动窗口,所以本身也没什么问题;
但对于
错的就是几种情况,就是一开始窗口初始化的时候,是否应该给maxIndex赋值,如果赋值的话,那肯定是赋值成maxJump,这个值,对应的是哪个下标的?说不清楚
如果不赋值,那么遍历的时候,从哪里开始遍历?之前是maxIndex+1,如果从MaxIndex,那这个下标的窗口就是[0,maxIndex-minIndex]
即,如果还是从MaxIndex-minIndex来移动右端点赋值的话,一开始的maxIndex是0,就会忽略掉中间的可能最大值
所以对于maxJump的起点的情况,对于这个下标的滑动窗口,要完整判断其窗口内的所有元素,因为除了其最有端点maxJump-minJump,其前面的所有点,都是没有元素来检验入队过的

这个样例没过是因为,当前的0确实是不可达的,是不能被其窗口里的值所能到达的,但当前的0不能被到达,不意味着到达不了终点
就是前面,比如前一个0可达,那就可以到达比当前这个0更远的地方,所以这个提前终止的判断就是不对的
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n=s.size(),maxIndex=0;;
if(s[n-1]!='0'){return false;}
vector<bool>flag(n,false);//[i-maxJump,i-minJump]
for(int i=minJump;i<=maxJump;i++){
//flag[i]=s[i]=='0';
if(s[i]=='0'){
flag[i]=true;
//maxIndex=i;
}
}
for(int i=0;i<=maxJump-minJump;i++){
if(flag[i]){maxIndex=i;}
}
for(int i=maxJump;i<n;i++){
cout<<"cur i:"<<i<<"当前元素为"<<s[i]<<"cur maxIndex"<<maxIndex<<endl;
if(s[i-minJump]=='0'&&flag[i-minJump]){maxIndex=i-minJump;}
if(s[i]!='0')continue;
if(maxIndex<i-maxJump){return false;}
//if(maxIndex>i-minJump){return false;}
//if(s[i]=='0'){flag[i]=true;}
flag[i]=true;
}
return true;
}
};
class Solution {
public:
bool canReach(string s, int minJump, int maxJump) {
int n=s.size(),cnt=0;;
if(s[n-1]!='0'){return false;}
vector<bool>flag(n,false);//[i-maxJump,i-minJump]
flag[0]=true;
for(int i=minJump;i<=maxJump;i++){
if(s[i]=='0'){
flag[i]=true;
}
}
for(int i=0;i<=maxJump-minJump;i++){
if(flag[i]){cnt++;}
}
for(int i=maxJump+1;i<n;i++){
//cout<<"cur i:"<<i<<"当前元素为"<<s[i]<<"cur maxIndex"<<maxIndex<<endl;
if(flag[i-maxJump-1]){cnt--;}
if(flag[i-minJump]){cnt++;}
flag[i]=(cnt>0)&&s[i]=='0';
}
// for(int i=0;i<n;i++){
// cout<<flag[i]<<" ";
// }
return flag[n-1];
}
};
319
就是统计每个数的因数数量
如果是奇数,最后就是开,否则就是偶数
如何快速求解因数的数量?如何证明完全平方数的因数数量是奇数?


树上背包

那如何知道处理顺序?就是能够在处理完3和4后,找到1,处理完1后找到2

如果DP,那dp[i][j]应该表示?表示在能选j个课的情况下,由i为根节点所展开的子树,所能达到的最大值
如果他有两个子节点l和r
如果由i展开,那i根节点必然被选择,
所以dp[i][j]=max(dp[l][j-1],dp[r][j-1])+num[i]吗
然后对于定值,就是
先构建树,如何构建树?
然后对这个树进行后序遍历,在叶子节点,那么就是dp[i][j]=num[i]
最后返回根节点的dp值
那就是说再加一重循环,从0到j-1,表示分给左子树的任务数量
即dp[i][j]=max(for 0->j-1)(dp[l][k]+dp[r][j-1-k]))+num[i]
不应该就一个循环码?对于每个根节点,这个v是什么?什么叫分配给前面子树的容量?
就是说,这个数不是二叉树,而是可能有多个孩子的,有多个子树的
那确实是好几重循环
先是遍历每个孩子节点,然后确定这个孩子节点要花几个容量
怎么列式?好复杂,
假设根节点有ABCD等子节点,根节点有J个容量,合并是什么意思?看起来,有多少个孩子,就有几个循环,循环的内容就是确定分给每个孩子的容量数量,其加和为j-1,即对于A,有a为分给它的数量,从0循环到j-1,然后有b为分给B的数量,从0循环到j-1-a,直到最后一个孩子节点
然后DP就是
dp[i][j]=max(dp[A][a]+dp[B][b]+dp[C][c]+dp[D][d]+num[i],dp[i][j])
这个循环,就是说是除了自己以外的其它孩子节点所分到的容量数量
但是这样,如何体现出各种孩子之间的组合?
就是说当在遍历孩子A的时候,给自己分的是k,那么分给其它孩子的容量就是v-k,那就必须得知道dp[i][v-k]
目前每个孩子的dp[][*]都是知道的,因为是后序处理,所以当处理到根节点时,子节点的DP都已经确定了;
每个孩子下的容量v是什么物理含义?不应该就是分给某个孩子的容量吗?取值不就是0到j-1吗
即给自己k个,与给其他人j-1-k个的组合
知道了给其他人j-1-k个时的最佳组合,那最优就是max(dp[A][k]+其它孩子在j-1-k时的最优组合+num[i],dp[i][j])
感觉说白了还是当成了一个二叉树处理了,即看成了自己和别人,即使这个别人可能包含多个元素
但不对吧
这样看不就一个循环,即分给这个孩子的容量v的循环吗?为什么内部还有k的循环,k是什么意思?
我现在想的DP过程就是,
遍历所有孩子
然后DP[i][j]=max(dp[i][j-1-v],dp[这个孩子][v]+dp[i][v]+num[i])
不过这样的话,dp[i][v]就没被赋值,????
但是这个应该是更顶层要做的事吧?不对,好像就是本层要确定的?好乱
那如果给定dp[i][j]后,dp[i]的0到j-1都要自己确定,那整个树上DP的进行过程是怎样的?
就是先构建了一颗依赖树,然后说只能上K门课
对于根节点,要确定根节点的DP值,就要知道所有孩子节点的DP值,那在确定孩子节点DP值的时候,对于容量,应该是怎样的范围?
就是DP赋值的顺序和上限是怎么确定?

这是对于树形的情况,如果是图上的情况,怎么搞?就是每个节点的入度和出度最大不再是1,而可能是更大?
那对于森林的情况,如果加上了学分为0的0号根节点,那意味着在原来的M门课基础上,要再多修一门0号课程呗,这也是为什么要用m+2而不是m+1的原因吗?;但是之前的合并写法,将对根节点访问也包含了,这是否意味着对根节点的访问方案是可以被换掉的?就是相较于访问根节点,将这个容量全部给到子节点上会取得更高的收益情况下,会更新DP吗?这样的话不就违背了,就是说必须强制访问根节点的
线段树
现在是直接给定的一个数列;对于长度为n为的数列,就是以N/2,即中点来划分左右子树,前一半是左子树,后一半是右子树;
对于[x,y]区间上加上k的操作,判断y与中点mid的关系,如果小于,就说明全部都在左区间上
否则有在有区间的部分
再判断x与中点mid的关系,如果大于,就说明全在右区间上
两个判断都不满足,那就说明左右都有,对于左区间,应该是[x,mid];右区间应该是[mid+1,y]
那对于修改,好像还是一个一个地去修改树上的节点,还是On
对于求和,把递归的部分加起来
不过加速的部分在哪?如果是y刚好等于mid的情况,应该可以不再递归,x等于mid+1的情况也是
即取等时的情况


就是说N是树的底层叶子节点,先是要保证把它扩充成2的整数次幂,最差情况是刚好过了1
为2N即可,
现在知道了N个叶子节点,怎么由这N个节点构筑出来线段树?