观察优化dp下
规划兼职工作
思路:先按照end排序,分成两种情况,第一种不选当前的工作,第二种选择当前的工作那么就要找前面工作的结尾小于等于当前工作的开始的dp值(可以当天结束当天开始)。两者比较大小
class Solution {
private:
struct node{
int st,en,pro;
};
int find(vector<node>& job,int i,int t){
int l=0,r=i,mid,ans=-1;
while(l<=r){
mid=l+(r-l)/2;
if(job[mid].en<=t){
ans=mid;
l=mid+1;
}else r=mid-1;
}
return ans;
}
public:
int jobScheduling(vector<int>& star, vector<int>& en, vector<int>& profit) {
int n=profit.size();
vector<node>job(n,{0,0,0});
for(int i=0;i<n;i++){
job[i].st=star[i];
job[i].en=en[i];
job[i].pro=profit[i];
}
sort(job.begin(),job.end(),[](const node&a,const node&b){
return a.en<b.en;
});
int dp[(int)5e4+1]={0};
dp[0]=job[0].pro;
for(int i=1;i<n;i++){
dp[i]=job[i].pro;
if(job[i].st>=job[0].en){
dp[i]+=dp[find(job,i-1,job[i].st)];
}
dp[i]=max(dp[i],dp[i-1]);
}
return dp[n-1];
}
};
k个逆序对个数
思路:dp[i][j]
表示第i
个数字的时候有j
个逆序对的种数
对于一个序列来说,无论谁先出现谁后出现,最后的逆序对个数是恒定的,不妨让每次都让更大的数字出现,这样只需要考虑自己对其他人的影响就可以了,而不用考虑别人对自己的影响
每次都求最后一个加入到序列里面的数后的逆序对个数,当i<=j
的时候,前面一共i-1
个数字,所以有i
个位置可以放入,求和,当i>j
的时候,只有j+1
(0到j一共j+1
)种情况(总的来说就是情况数就是i和j+1
中小的那个)
class Solution {
public:
int kInversePairs(int n, int k) {
int mod=1e9+7;
int dp[1001][1001]={0};
dp[0][0]=1;
for(int i=1;i<=n;i++){
dp[i][0]=1;//无论多少个数字,0个逆序对都只有一种
for(int j=1,window=1;j<=k;j++){//window刚开始就有dp[i][0]的值,所以是1开始
if(j<i){
window=(window+dp[i-1][j])%mod;
}else {
window=((window+dp[i-1][j])%mod-dp[i-1][j-i]+mod)%mod;//三个mod一个都别少
}
dp[i][j]=window;
}
}
return dp[n][k]%mod;
}
};
自由之路
为什么要这样设:对于递归的总入口,要是包含全局的才可以,所以一共两种:i位置解决j往后或者i位置解决j往前,但是字符匹配必须从开头开始,而后一种情况没办法提供任何有关开头的信息,所以就选择前者
贪心:转的时候只需要尝试顺逆方向最近的两个就可以(特殊情况,如果当前位置就匹配当前字符,那么就不需要转了)
class Solution {
private:
// dp[i][j]:记忆化存储在ring的i位置匹配key的j位置时的最小步数
int dp[101][101]={0};
// rin[c][k]:存储字符c('a'-'z')在ring中第k次出现的位置索引
int rin[26][101]={0};
// size[c]:记录字符c在ring中出现的总次数
int size[26]={0};
// 查找字符在ring中位置大于t的最小索引(顺时针方向最近)
int find1(int key,int t){
int l=0,r=size[key]-1,mid,ans=-1;
while(l<=r){
mid=(l+r)/2;
if(rin[key][mid]>t){
ans=mid;
r=mid-1;
}else l=mid+1;
}
// 不是返回0或者ans,而是这个rin索引对应的值,也就是在字符串中的实际索引
// 未找到时返回该字符第一次出现的位置(环形循环)
return ans==-1?rin[key][0]:rin[key][ans];
}
// 查找字符在ring中位置小于t的最大索引(逆时针方向最近)
int find2(int key,int t){
int l=0,r=size[key]-1,mid,ans=-1;
while(l<=r){
mid=(l+r)/2;
if(rin[key][mid]<t){
ans=mid;
l=mid+1;
}else r=mid-1;
}
// 未找到时返回该字符最后一次出现的位置(环形循环)
return ans==-1?rin[key][size[key]-1]:rin[key][ans];
}
// 递归计算最小步数:当前在ring的i位置,需匹配key的j位置
int f1(int i,int j,string &ring,int m,string &key,int n){
// 终止条件:所有字符匹配完成
if(j==n)return 0;
// 记忆化:直接返回已计算的结果
if(dp[i][j]!=-1)return dp[i][j];
int ans=0;
// 当前位置字符匹配,按按钮(+1步)并处理下一个字符
if(key[j]==ring[i]){
ans=1+f1(i,j+1,ring,m,key,n);
}else {
// 不匹配时需要旋转到目标字符位置
int jump1=find1(key[j]-'a',i); // 顺时针目标位置
// n还是m,i还是j都要弄清楚(此处使用m,因为是ring的长度)
// 计算顺时针旋转距离:直接向右或环形绕回
int dis1=(jump1>i)?(jump1-i):(m-i+jump1);
int jump2=find2(key[j]-'a',i); // 逆时针目标位置
// 计算逆时针旋转距离:直接向左或环形绕回
int dis2=(i>jump2)?(i-jump2):(m-jump2+i);
// 两个dis不需要再+1,因为按下按钮算在递归到下一步的里面了,j没变成j+1,换成这里dis+1,递归里面j+1同样是对的
// 取两种旋转方式的最小步数(旋转距离+后续步骤)
ans=min(dis1+f1(jump1,j,ring,m,key,n),dis2+f1(jump2,j,ring,m,key,n));
}
dp[i][j]=ans; // 记录当前状态结果
return ans;
}
public:
// 计算拼写关键词所需的最少旋转+按按钮步数
int findRotateSteps(string ring, string key) {
int n=key.size(),m=ring.size();
memset(dp,-1,sizeof(dp)); // 初始化dp为-1(未计算状态)
// 预处理:记录每个字符在ring中出现的所有位置
for(int i=0;i<m;i++){
rin[ring[i]-'a'][size[ring[i]-'a']++]=i;
}
// 从ring的0位置开始,匹配key的第0个字符
return f1(0,0,ring,m,key,n);
}
};
未排序数组中累加和小于或等于给定值的最长子数组长度
法一:思路:找最前面大于等于此位置前缀和-k的递增前缀和的位置
为什么要加一个递增前缀和,因为只有递增才可以二分,如果没有这个,就需要map,但是map没办法搜范围,因为目标是搜最长的,因此所需要的答案也就是最左边的,后面的被覆盖的数据本身就是无效的
比如10 12 10 10,搜大于等于10的最左边,显然就是最前面的那个,而不是后面的两个10,因此把后面两个10改为12不会影响最后的结果
做了一些改动,将所有数组都平移了一个,此时前缀和的值是算上当前值的,所以不需要再+1了
因为搜索返回的答案都是不需要的,这个值的下一个才是开端,正好把+1抵消掉了
比如前缀和数组prefix是2 4 5,搜索k=2的,那么就是搜索大于等于5-2的最左边,也就是4的位置,但是4所在的位置本身是不要的(因此l也是从0开始而不是1)
也不需要特殊考虑找不到的情况,因为把二分的初始值换成了很大的值,算出来也是负数,不会影响结果
#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5;
ll n,k,g[N],prefix[N],sum,ans,tmp;
int findd(int t,int rr){
int l=0,r=rr,mid,ans=n+1;
while(l<=r){
mid=l+((r-l)>>1);
if(prefix[mid]>=t){
ans=mid;
r=mid-1;
}else l=mid+1;
}
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>g[i];
sum+=g[i];
prefix[i]=max(sum,prefix[i-1]);
ans=max(ans,i-(ll)findd(sum-k,i));
}
cout<<ans<<endl;
return 0;
}
#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5;
ll n,k,g[N],minsum[N],minsumend[N],ans;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=0;i<n;i++)cin>>g[i];
minsum[n-1]=g[n-1];
minsumend[n-1]=n-1;
for(int i=n-2;i>=0;i--){
minsum[i]=minsum[i+1]<0?minsum[i+1]+g[i]:g[i];
minsumend[i]=minsum[i+1]<0?minsumend[i+1]:i;
}
for(int i=0,sum=0,end=0;i<n;i++){
while(end<n&&sum+minsum[end]<=k){
sum+=minsum[end];
end=minsumend[end]+1;//end的迭代,只有第一次end等于i,后面都不等于
}
if(end>i){
ans=max(ans,(ll)end-i);
sum-=g[i];
}else {
end=i+1;//end++;
}
}
cout<<ans<<endl;
return 0;
}