线段树优化dp

abc339 E - Smooth Subsequence

在这里插入图片描述

思路:

我们很容想到一个 n n n方的的状态转移方程,即对于每个i,我们去枚举 1 1 1 i − 1 i-1 i1的状态,即
d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) ; dp[i]=max(dp[i],dp[j]+1); dp[i]=max(dp[i],dp[j]+1);
但是由于时间的限制,我们无法使用n方的dp去完成这道题目,考虑进行优化,因为对于状态 i i i,其状态只能由前面点的状态转移过来,我们同样可以换一种说法,对于值 a [ i ] a[i] a[i],其状态只能由 ( a [ i ] − d , a [ i ] + d ) (a[i]-d,a[i]+d) (a[i]d,a[i]+d)这个范围内的数转移过来,我们想要长度最大,那么我们把值域当作下标进行维护,答案即为范围内的最大值,计算完当前状态后,我们再把当前的状态放入所谓的值域中。
对于上述操作,将值域看作下标后,其本质就是区间查询最大值,然后单点进行修改,因此线段树完全可以代替第二层循环,将时间复杂度降低到log级别。

#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<int, 5> ar;
int mod = 1e9+7;
// const int maxv = 4e6 + 5;
// #define endl "\n"

int dp[N];
int a[N];

struct node
{
    int l,r,val;
    #define l(x) tr[x].l
    #define r(x) tr[x].r
    #define val(x) tr[x].val
}tr[N*4];

void update(int p)
{
    val(p)=max(val(p*2),val(p*2+1));
}

void build(int p,int l,int r)
{
    if(l==r){
        tr[p]={l,r,0};
        return ;
    }
    l(p)=l,r(p)=r;
    int mid=(l+r)/2;
    build(p*2,l,mid),build(p*2+1,mid+1,r);
}

void change(int p,int pos,int x)
{
    if(l(p)==r(p)){
        val(p)=x;
        return ;
    }
    int mid=(l(p)+r(p))/2;
    if(pos<=mid) change(p*2,pos,x);
    else change(p*2+1,pos,x);
    update(p);
}

int query(int p,int l,int r)
{
    if(l<=l(p)&&r(p)<=r) return val(p);
    int mid=(l(p)+r(p))/2;
    int res=0;
    if(l<=mid) res=max(res,query(p*2,l,r));
    if(r>mid) res=max(res,query(p*2+1,l,r)); 
    return res;
}


void solve()
{
    int n,d;
    cin>>n>>d;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        // dp[i]=1;
    }
    build(1,1,N);
    for(int i=1;i<=n;i++){
        int cur=query(1,max(0,a[i]-d),min(N,a[i]+d));
        dp[i]=cur+1;
        change(1,a[i],dp[i]);
    }
    cout<<query(1,1,N)<<endl;
} 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    // cin>>t;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}

abc353 G - Merchant Takahashi

在这里插入图片描述

思路:

和上题一样,很容易想到一个 n 2 n^2 n2的dp转移,即
d p [ i ] = max ⁡ j = 1 j = i − 1 ( d p [ j ] − c ∣ t i − t j ∣ + p i ) dp[i]=\max_{j=1}^{j=i-1} (dp[j]-c|t_i-t_j|+p_i) dp[i]=j=1maxj=i1(dp[j]ctitj+pi)
那么考虑如何进行优化,对于递推方程,我们唯一难处理的地方就是绝对值,所以我们把绝对值拆开,进行分类讨论即可,那么上述式子则变成了
d p [ i ] = max ⁡ j = 1 j = i − 1 ( d p [ j ] + c × t j ) − c × t i + p i dp[i]=\max_{j=1}^{j=i-1}(dp[j]+c\times t_j)-c\times t_i+p_i dp[i]=j=1maxj=i1(dp[j]+c×tj)c×ti+pi
d p [ i ] = max ⁡ j = 1 j = i − 1 ( d p [ j ] − c × t j ) + c × t i + p i dp[i]=\max_{j=1}^{j=i-1}(dp[j]-c\times t_j)+c\times t_i+p_i dp[i]=j=1maxj=i1(dp[j]c×tj)+c×ti+pi
对于 m a x max max部分都是关于 j j j的变量,说的更具象一点,则是对于当前位置 t i t_i ti,只能从 t j t_j tj转移而来,所以我们维护每个 t j t_j tj对应位置的值即可,那么则变为了查询区间最大值,使用线段树维护就行。

#include <bits/stdc++.h>

using namespace std;
const int N = 3e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<ll, 3> ar;
const int mod = 1e9+7;
const int maxv = 4e6 + 5;
// #define endl "\n"
template<const int T>
struct ModInt {
    const static int mod = T;
    int x;
    ModInt(int x = 0) : x(x % mod) {}
    ModInt(long long x) : x(int(x % mod)) {} 
    int val() { return x; }
    ModInt operator + (const ModInt &a) const { int x0 = x + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
    ModInt operator - (const ModInt &a) const { int x0 = x - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
    ModInt operator * (const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
    ModInt operator / (const ModInt &a) const { return *this * a.inv(); }
    bool operator == (const ModInt &a) const { return x == a.x; };
    bool operator != (const ModInt &a) const { return x != a.x; };
    void operator += (const ModInt &a) { x += a.x; if (x >= mod) x -= mod; }
    void operator -= (const ModInt &a) { x -= a.x; if (x < 0) x += mod; }
    void operator *= (const ModInt &a) { x = 1LL * x * a.x % mod; }
    void operator /= (const ModInt &a) { *this = *this / a; }
    friend ModInt operator + (int y, const ModInt &a){ int x0 = y + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
    friend ModInt operator - (int y, const ModInt &a){ int x0 = y - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
    friend ModInt operator * (int y, const ModInt &a){ return ModInt(1LL * y * a.x % mod);}
    friend ModInt operator / (int y, const ModInt &a){ return ModInt(y) / a;}
    friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x;}
    friend istream &operator>>(istream &is, ModInt &t){return is >> t.x;}

    ModInt pow(int64_t n) const {
        ModInt res(1), mul(x);
        while(n){
            if (n & 1) res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }
    
    ModInt inv() const {
        int a = x, b = mod, u = 1, v = 0;
        while (b) {
            int t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        if (u < 0) u += mod;
        return u;
    }
    
};
using mint = ModInt<998244353>;

ll a[N];

struct segment_tree
{
struct node
{
    ll l,r,val;
    #define l(x) tr[x].l
    #define r(x) tr[x].r
    #define val(x) tr[x].val
}tr[N*4];

void update(int p)
{
    val(p)=max(val(p*2),val(p*2+1));
}

void build(int p,int l,int r)
{
    if(l==r){
        tr[p]={l,r,a[l]};
        return ;
    }
    l(p)=l,r(p)=r;
    int mid=(l+r)/2;
    build(p*2,l,mid),build(p*2+1,mid+1,r);
    update(p);
}

void change(int p,int pos,ll x)
{
    if(l(p)==r(p)){
        val(p)=x;
        return ;
    }
    int mid=(l(p)+r(p))/2;
    if(pos<=mid) change(p*2,pos,x);
    else change(p*2+1,pos,x);
    update(p);
}

ll query(int p,int l,int r)
{
    if(l<=l(p)&&r(p)<=r) return val(p);
    int mid=(l(p)+r(p))/2;
    ll res=-1e18;
    if(l<=mid) res=max(res,query(p*2,l,r));
    if(r>mid) res=max(res,query(p*2+1,l,r)); 
    return res;
}
};

segment_tree t1,t2;

void solve()
{
    ll n,c;
    int m;
    cin>>n>>c;
    cin>>m;
    vector<pll> inf(m+5);
    for(int i=1;i<=m;i++){
        ll t,p;
        cin>>t>>p;
        inf[i]={t,p};
    }
    a[1]=0;
    for(int i=2;i<=n;i++) a[i]=-1e18;
    t1.build(1,1,n);
    t2.build(1,1,n);
    ll ans=0;
    for(int i=1;i<=m;i++){
        auto &[t,p]=inf[i];
        int id=t;
        t--;
        ll dp1=t1.query(1,1,id)-c*t;//因为是分类讨论,当ti>tj时,对应的tj范围是1到ti;
        ll dp2=t2.query(1,id,n)+c*t;//同理
        ll dp=max(dp1,dp2)+p;//计算出当前点的最大值
        ans=max(ans,dp);
        t1.change(1,id,dp+1ll*c*t);//分别进行更新
        t2.change(1,id,dp-1ll*t*c);
    }
    cout<<ans<<endl;
}



int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	t = 1;
	// cin >> t;
	while (t--)
	{
		solve();
	}
	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值