25杭电暑期第八场

1001

询问分治 dp

给一张图,点很少,边很多。每个询问给出一个区间,只能用边权在这个区间内的边,选一条路径,路径上边权严格递增,求最长路径(长度和权值是两个东西)

每一个询问,其实是个典,可以来一个O(m)O(m)O(m)的dp,扫一遍边转移即可得到最长路径。

但这里询问很多,肯定需要我们一块处理。并且每个询问还有边权的约束。

注意到对边权排序后,每个询问实际上是序列上的一个子区间dp的结果。这就可以转化成一个经典模型:对要进行dp的序列进行分治,每一层[L,R][L,R][L,R]中,处理所有询问区间[l,r][l,r][l,r]跨过[L,R][L,R][L,R]中点的询问(这些询问只能在这一层处理,更低层处理不了)。询问给的原始形式是边权的范围,我们需要二分找到可以包含这个边权范围的,最小的边权序列的区间,也就是转化成边权序列上的一个区间。

处理询问当然也不能对每个询问都一遍dp,注意到每个询问都是由[l,mid][mid+r][l,mid][mid+r][l,mid][mid+r]拼起来的,我们可以先预处理[L,mid][mid+1,R][L,mid][mid+1,R][L,mid][mid+1,R]的后缀和前缀dp结果,然后每个询问显然可以用一个[L,mid][L,mid][L,mid]的后缀和一个[mid+1,R][mid+1,R][mid+1,R]的前缀拼起来

这样也就是对于[L,mid][L,mid][L,mid]倒着dp,对[mid+1,R][mid+1,R][mid+1,R]正着dp。

回答询问需要先把每个询问[l,r][l,r][l,r]放在lll位置的桶里,然后遍历当前区间左半边[L,mid][L,mid][L,mid]的所有桶,看这些桶里的询问,有哪些是跨过中点的,就回答。

检查哪些询问是跨过中点的,不太能直接遍历每个桶里的所有询问,可能超时。可以对询问的右端点升序排序,然后从后面较大的rrr开始检查

PSPSPS:这玩意牛客寒假营似乎出过,我还补了,但还是没看出来。。

struct edge{
	int u,v,w,d;
}e[N];
struct query{
	int l,r;
}q[N];
int mx1[N][60],mx2[N][60];
void solve()
{
	int n,m,Q;
	cin>>n>>m>>Q;
	rep(i,1,m){
		cin>>e[i].u>>e[i].v;
		cin>>e[i].w>>e[i].d;
	}
	sort(e+1,e+1+m,[](auto i,auto j){
		return i.w<j.w;
	});
	vi w(m+1);
	rep(i,1,m)w[i]=e[i].w;
	
	vvi bin(m+1);
	vi ans(Q+1);
	rep(i,1,Q){
		cin>>q[i].l>>q[i].r;		
		q[i].l=lower_bound(w.begin(),w.end(),q[i].l)-w.begin();
		q[i].r=upper_bound(w.begin(),w.end(),q[i].r)-w.begin()-1;
//		cout<<q[i].l<<' '<<q[i].r<<'\n';
		if(q[i].l<=m){
			bin[q[i].l].push_back(i);
		}
	}
	rep(i,1,m){
		sort(bin[i].begin(),bin[i].end(),[&](int i,int j){
			return q[i].r<q[j].r;
		});
	}
	
	auto &&work=[&](auto &&work,int L,int R)->void{
		int cnt=0;
		rep(i,L,R){
			cnt+=bin[i].size();
		}	
		if(!cnt)return;
		if(L>R)return;
		if(L==R){
			for(int id:bin[L]){
				if(q[id].r>=R)ans[id]=e[L].d;
			}
			return;
		}
		
		int mid=(L+R)>>1;
		vvi f(n+1,vi(n+1,-1e18)),g(n+1,vi(n+1,-1e18));
		rep(i,1,n){
			f[i][i]=0;
			g[i][i]=0;
		}
		
		rep(i,0,mid-L+1){
			rep(x,1,n){
				mx1[i][x]=0;
			}
		}
		rep(i,0,R-mid){
			rep(x,1,n){
				mx2[i][x]=0;
			}
		}
		
		int len=0;
		for(int i=mid;i>=L;i--){
			++len;
			int u=e[i].u,v=e[i].v,d=e[i].d;
			rep(x,1,n){
				f[u][x]=max(f[u][x],f[v][x]+d);
				mx1[len][x]=max(mx1[len-1][x],f[u][x]);
			}
		}
		
		len=0;
		rep(i,mid+1,R){
			++len;
			int u=e[i].u,v=e[i].v,d=e[i].d;
			rep(x,1,n){
				g[v][x]=max(g[v][x],g[u][x]+d);
				mx2[len][x]=max(mx2[len-1][x],g[v][x]);
			}
		}
		
		
		rep(i,L,mid){
			while(bin[i].size()&&q[bin[i].back()].r>mid){
				int id=bin[i].back();
				int r=q[id].r;
				int len1=mid-i+1;
				int len2=r-mid;
//				if(L==1&&R==5)cout<<id<<' '<<len1<<' '<<len2<<'\n';
				rep(x,1,n){
					ans[id]=max(ans[id],mx1[len1][x]+mx2[len2][x]);
				}
				bin[i].pop_back();
			}
		}
		work(work,L,mid);
		work(work,mid+1,R);
	};
	work(work,1,m);
	rep(i,1,Q){
		cout<<ans[i]<<'\n';
	}
}
	
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int test=1;	
	
    cin>>test;
    while(test--){	
    	solve();	
    }
}

1004

思维 结论

全是结论,找到结论后很好写。

给两个序列,每次可以把一个长度k的子区间循环移位一次,问能否把两个序列变得完全相同?

首先显然必须两个序列的可重复集完全相同,也就是每种元素出现次数都相同。然如果k=1k=1k=1,无法进行操作,必须初始就相同。如果k=nk=nk=n只能整体循环,考虑最小表示法,也就是通过循环,把两个字符串都变成字典序最小的形式,再比较这两个形式是否相同。

PSPSPS:最小表示法其实是种思想,比较两个东西能否在某些操作后完全相同,考虑一个基准,把两个都变成这个基准下的,再来比较是否相同,在这里就是字符串变成字典序最小的形式,如果最小表示下相同,那说明两个东西都能变成这个最小表示,显然可以把一个变成最小表示,再从最小表示变成另一个,也就可以使两个东西相同

回到一般情况,如果k是偶数,我们总能交换相邻元素,手玩一下就能发现。所以就能对整个序列进行排序,肯定可以变成相同的,这是个重要结论。

然后k是奇数,通过归纳法(这块我也没懂),可以证明直到还剩两个元素,前面的都可以排序,所以就看最后这俩元素是不是相同的。

如果存在一种元素至少出现两次,我们可以把这种元素的两次出现放在最后,那a,b序列前面都排序了,完全相同,最后一种元素出现两次,ab也相同,那就可以全都相同。

如果没有一种元素出现至少两次,最后剩的这俩元素,可能是x,yx,yx,y也可能是y,xy,xy,x,要看a,b里这俩元素的顺序是否相同。但这显然不能模拟,太慢了。

注意到这两种情况的逆序对奇偶是相反的,并且k位的循环移位操作,相当于把一个元素进行k-1次相邻交换,每次相邻交换整个序列的逆序对奇偶反转,k是奇数,k-1是偶数,反转偶数次仍然不变,也就是k是奇数的话循环移位不改变整个序列的逆序对个数奇偶性。那么最后剩下这俩元素的逆序奇偶,和初始整个序列的逆序对奇偶是相同的。

我们想知道最后只剩两个元素未排序是的逆序对奇偶,直接对初始序列计算逆序对奇偶即可。也就是比较初始ab的逆序对奇偶是否相同。

总的来说,有这么几个结论

  • k为偶数,k循环移位等价于可以交换任意相邻元素
  • k=n,只能对整体循环移位,考虑最小表示法
  • 序列中有元素出现超过1次,不论k奇偶都可以全部排序
  • k为奇数,k循环移位不改变序列逆序对奇偶性
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
 
inline int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

const int N = 1e5 + 10;
int n, k;
int a[N], A[N];
int b[N], B[N];
int s[N];

int find(int *a) {
    rep(i, 0, n - 1) s[i] = a[i + 1];

    int i = 0, j = 1, k = 0;
    for(; i < n && j < n && k < n; ) 
        if(s[(i + k) % n] == s[(j + k) % n])
            k ++;
        else {
            s[(i + k) % n] > s[(j + k) % n] ? i = i + k + 1 : j = j + k + 1;
            if (i == j) i++;
            k = 0;
        }
    
    return min(i, j) + 1;
}

int c[N];
void add(int x) {for(; x <= n; x += (x & -x)) c[x] ++;}
int ask(int x) {int s = 0; for(; x; x -= (x & -x)) s += c[x]; return s;}

int work(int *a) {
    int s = 0;
    rep(i, 1, n) c[i] = 0;
    per(i, n, 1) {
        int x = lower_bound(A + 1, A + n + 1, a[i]) - A;
        s ^= (ask(x) & 1);
        add(x);
    }
    return s;
}

void solve() {
    n = read(), k = read();
    rep(i, 1, n) a[i] = A[i] = read();
    rep(i, 1, n) b[i] = B[i] = read();

    sort(A + 1, A + n + 1);
    sort(B + 1, B + n + 1);

    bool flag = true;
    rep(i, 1, n) if(A[i] != B[i]) {flag = false; break;}

    if(!flag) return puts("NO"), void();

    if(n == k) {
        int i = find(a);
        int j = find(b);

        bool flag = true;
        rep(k, 1, n) {
            int x = i + k - 1 > n ? i + k - 1 - n : i + k - 1;
            int y = j + k - 1 > n ? j + k - 1 - n : j + k - 1;
            if(a[x] != b[y]) {flag = false; break;}
        }
        return puts(flag ? "YES" : "NO"), void();
    }

    if(k == 1) {
        bool flag = true;
        rep(i, 1, n) if(a[i] != b[i]) {flag = false; break;}
        return puts(flag ? "YES" : "NO"), void();
    }

    if(!(k&1)) return puts("YES"), void();

    rep(i, 1, n - 1) if(A[i] == A[i + 1]) {puts("YES"); return;}

    int i = work(a);
    int j = work(b);

    puts(i == j ? "YES" : "NO");
}

int main() {
	int T = read(); while(T --) solve(); return 0;
}

1005

树的直径

树上问所有直径的公共边有哪些,有动态加点

如果没有加点,先找到一条直径,所有直径的公共边,一定是这条直径上的边的子集,并且,一定是这条直径上的一个子区间。具体来说,这个区间的左右端点满足这样的条件:从区间端点到直径端点的距离,和从区间端点不经过直径可到达的最大距离,一定相等,也就是直径可以在这里分叉,有至少两种路径都是直径,那么这个点分叉后的边,就都不是所有直径的公共边了。

那么可以把这颗树的形态转换成:在直径这条链上,所有直径的公共边是这条链形成的序列的一个区间,每个点可以长出一棵树,长出的树有一个深度。如果树的深度等于这一点到直径端点的距离,这个点就是直径的公共边区间的端点。

显然我们计算直径链上每个点出发,不走这条直径边,可以到达的最大深度,然后判断深度和这个点到直径端点的距离,即可判断出每个点是否是区间端点。

动态加点的话,有两种,一种是加在直径端点上了,那这条直径会比原本的其他直径都更长,成为新的,唯一的直径,不妨设加在直径右边了,那公共边区间的右端点,会移动新直径的最右边。

另一种是加在其他点上,也就是直径中间的点长出来的子树上,那对结果的影响就是,可能导致这个点所在子树的深度变大,也就是直径上对应点的最大深度变大,变大之后可能导致最大深度等于根到直径端点的距离,导致树根成为公共边区间的端点。

所以我们需要点对直径上的每个点,维护这个点长出的树的最大深度,并且为了加点后快速深度,需要记录每个点所在子树的根是直径上的哪个点,这是可以的,因为新加的点肯定和他的父亲的根是相同的,根据父亲记录即可,并且新加点的深度我们也要知道,才能更新子树最大深度,这也是容易的,也是因为父亲的深度我们可知,新加点的深度就是父亲深度+1

当然这题可做是因为保证了,无论何时,我们维护的这个直径一定都是直径,也就是不会出现在子树上加一个点,导致原本这条链不是直径了。

void solve()
{
	int n,w,L=1,R=2;
	cin>>n>>w;
	
	vector<int>fa(n+10),sum(n+10),d(n+10);
	fa[1]=1,fa[2]=2;
	sum[1]=0,sum[2]=w;
	int tot=2,len=2;
	
	rep(i,1,n){
		int op;
		cin>>op;
		if(op==1){
			int w;
			cin>>w;
			++tot,++len;
			
			R=len;
			sum[len]=sum[len-1]+w;
			fa[tot]=len;
			d[tot]=0;
		}
		else if(op==2){
			int x,w;
			cin>>x>>w;
			
			++tot;
			
			fa[tot]=fa[x];
			d[tot]=d[x]+w;
			
			int pa=fa[tot];
			if(sum[pa]==d[tot]){
				L=max(L,pa);
			}
			if(sum[len]-sum[pa]==d[tot]){
				R=min(R,pa);
			}
		}
		else{
			cout<<sum[R]-sum[L]<<'\n';
		}
	}
}

1006

多tag线段树/广义矩阵乘,动态dp

给一个环形序列,问选一个子序列,不能连续选超过三个,的最大元素和。带修改

先考虑不带修,显然就是一个状态机dp,环形+长度为3的打家劫舍,记录当前结尾连续了几个即可,只有4个状态(结尾连续了0/1/2/3个)

环的话,考虑两种思路,一种是我们可以在dp状态里增加一个维度,记录开头选中了连续几个,另一种思路是可以跑4个dp,每次钦定开头选中了iii个,进行特殊的初始化,对这四种情况取最值。

对于第一种做法,发现我们就是要维护前后缀,这其实可以线段树维护,维护最长全0子串就是类似的思路。对每个区间维护前后缀分别选了[0,3][0,3][0,3]个的最优答案,然后合并时dpdpdp转移即可。但这样转移时可能需要枚举左区间和右区间的前后缀长度,合并复杂度时m4=256m^4=256m4=256的,有点大,可能被卡常。如果优化转移,变成m3=64m^3=64m3=64才能过。赛时我写这个做法被卡到怀疑人生。。

对于第二种,由于我们枚举了开头选几个,dp状态里就不用保存开头了,这个dp就很简单,而且由于不是开头结尾的信息都维护,就不能线段树合并来进行dp转移了。

但这个dp本身就是个简单的状态机dp,只用到了加法和取max操作,还带修,可以考虑广义矩阵乘维护动态dp来做。也就是我们把矩阵运算的乘法和加法,改成加法和取max,然后就能用一个4∗44*444矩阵表示这个状态机dp的转移方程,每个元素都会转移一次,也就是每个元素都有个转移矩阵,修改一个元素,就是修改这个矩阵的参数。

想知道所有元素的dp结果,由于矩阵也就是线性变换,是有结合率的,我们可以把状态逐步和所有矩阵相乘的转移过程,改成先让所有矩阵相乘,再把最终矩阵和状态向量乘起来。也就是我们要维护所有元素的转移矩阵乘起来的结果,这就是单点修,区间乘查询,可以线段树来做,我们把每个节点里增加一个矩阵,表示这个节点对应的区间的矩阵相乘的结果

为了防止被卡常,最好把矩阵用array实现,不要vector。并且最后所有元素的矩阵,可以左乘也可以右乘状态向量,但不同的方法,矩阵和状态向量都需要转置。

void print_Matrix(const Matrix &a){
	int n=a.size();
	int m=a[0].size();
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			cout<<a[i][j]<<' ';
		}
		cout<<'\n';
	}
}
// 矩阵乘法
Matrix multiply(const Matrix &A, const Matrix &B, long long MOD=M2)
{
    int n = A.size();
    int m = B[0].size();
    int k = B.size();
    Matrix C;
    rep(i,0,n-1){
		rep(j,0,m-1){
			C[i][j]=-1e9;
		}
	}

    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < m; ++j)
        {
            for (int l = 0; l < k; ++l)
            {
                C[i][j] = max(C[i][j] , A[i][l] + B[l][j] );
            }
        }
    }
    return C;
}
struct Tree
{
#define ls u << 1
#define rs u << 1 | 1
    struct Node
    {
        int l, r;
        Matrix m;

        Node operator+(const Node &o)
        {
            Node res;
            res.l = l;
            res.r = o.r;
            res.m=multiply(m,o.m);
            
            return res;
        }
    } tr[N << 2];

    void pushup(int u)
    {
        tr[u] = tr[ls] + tr[rs];
    }

    void build(int u, int l, int r,vi &a)
    {
        tr[u].l=l;
        tr[u].r=r;
        
        if (l == r){
        	tr[u].m={
				{
//					{0,0,0,0},
//					{a[l],-M1,-M1,-M1},
//					{-M1,a[l],-M1,-M1},
//					{-M1,-M1,a[l],-M1}
					{0,a[l],-M1,-M1},
					{0,-M1,a[l],-M1},
					{0,-M1,-M1,a[l]},
					{0,-M1,-M1,-M1}
				}
			};
            return;
        }
        int mid = (l + r) >> 1;
        build(ls, l, mid,a);
        build(rs, mid + 1, r,a);
        pushup(u);
    }

    void modify(int u, int idx,int val)
    {
        if (tr[u].l == tr[u].r)
        {
        	tr[u].m={
				{
//					{0,0,0,0},
//					{val,-M1,-M1,-M1},
//					{-M1,val,-M1,-M1},
//					{-M1,-M1,val,-M1}
					{0,val,-M1,-M1},
					{0,-M1,val,-M1},
					{0,-M1,-M1,val},
					{0,-M1,-M1,-M1}
				}
			};
            return;
        }
        else
        {
            int mid = (tr[u].l + tr[u].r) >> 1;
            if (mid >= idx)
                modify(ls, idx, val);
            else
                modify(rs, idx, val);
            pushup(u);
        }
    }

    Node query(int u, int l, int r)
    {
        if (l <= tr[u].l && tr[u].r <= r)
            return tr[u];
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (r <= mid)
            return query(ls, l, r);
        if (l > mid)
            return query(rs, l, r);
        return query(ls, l, r) + query(rs, l, r);;
    }
} t;
void solve()
{
	int n,q;
	cin>>n>>q;
	
	vi a(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	t.build(1,1,n,a);
	auto res=t.query(1,2,n);
	Matrix ans1=res.m;
	int ans=0;
	ans=max(ans,max({ans1[0][0],ans1[0][1],ans1[0][2],ans1[0][3]}));
	
	res=t.query(1,3,n);
	ans1=res.m;
	ans=max(ans,max({ans1[0][0],ans1[0][1],ans1[0][2]})+a[1]);
	
	res=t.query(1,4,n);
	ans1=res.m;
	ans=max(ans,max({ans1[0][0],ans1[0][1]})+a[1]+a[2]);	
	
	res=t.query(1,5,n);
	ans1=res.m;
	ans=max(ans,max({ans1[0][0]})+a[1]+a[2]+a[3]);	
	
	cout<<ans<<'\n';	
	

	rep(i,1,q){
		int x,v;
		cin>>x>>v;
		t.modify(1,x,v);
		a[x]=v;
		

		auto res=t.query(1,2,n);
		Matrix ans1=res.m;
		int ans=0;
		ans=max(ans,max({ans1[0][0],ans1[0][1],ans1[0][2],ans1[0][3]}));
		
		res=t.query(1,3,n);
		ans1=res.m;
		ans=max(ans,max({ans1[0][0],ans1[0][1],ans1[0][2]})+a[1]);
		
		res=t.query(1,4,n);
		ans1=res.m;
		ans=max(ans,max({ans1[0][0],ans1[0][1]})+a[1]+a[2]);
		
		res=t.query(1,5,n);
		ans1=res.m;
		ans=max(ans,max({ans1[0][0]})+a[1]+a[2]+a[3]);	
		
		cout<<ans<<'\n';
	}
}

1010

动态维护树的直径

按权值顺序加点,每次加到一个权值,问树的半径长度?

动态加点,求树的直径,这里有结论:新增一个点c,原本的直径是ab,那么新的直径只会是ab,ac,bc中的一个,我们比较它们的长度即可,树上路径长度,需要lca。

对于他这个加点,每次加到一个权值,把点按照权值排序,然后双指针即可。

int lg[N],f[N][20],idx[N],d[N];
void solve()
{
	int n=rd();
//	cin>>n;
	vi w(n+1);
	rep(i,1,n)w[i]=rd();
	
	vvi g(n+1);
	rep(i,1,n-1){
		int u=rd(),v=rd();
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	
	auto &&dfs=[&](auto &&dfs,int u,int fa)->void{
		f[u][0]=fa;
		rep(i,1,lg[d[u]]){
			f[u][i]=f[f[u][i-1]][i-1];
		}
		for(int v:g[u]){
			if(v==fa)continue;
			d[v]=d[u]+1;
			dfs(dfs,v,u);
		}
	};
    auto lca=[&](int x, int y)->int
    {
        if (d[x] < d[y])
            swap(x, y);
        while (d[x] > d[y])
            x = f[x][lg[d[x] - d[y]]];
        if (x == y)
            return x;
        for (int i = lg[d[x]]; i >= 0; i--)
            if (f[x][i] != f[y][i])
                x = f[x][i], y = f[y][i];
        return f[x][0];
    };
    auto dis=[&](int i,int j)->int{
		int LCA=lca(i,j);
		return d[i]+d[j]-2*d[LCA];	
	};
    dfs(dfs,1,-1);
    
    rep(i,1,n)idx[i]=i;
    sort(idx+1,idx+1+n,[&](int i,int j){
		return w[i]<w[j];
	});
	
	int a,b,dia=0;
	rep(i,1,n){
		if(w[i]==0){
			a=b=i;
			break;
		}
	}
		
	int j=1;
	rep(i,0,n-1){
		while(j<=n&&w[idx[j]]<=i){
			int x=idx[j];
			int ax=dis(a,x);
			int bx=dis(b,x);
			if(ax>bx){
				if(ax>dia){
					dia=ax;
					b=x;
				}
			}
			else if(bx>dia){
				dia=bx;
				a=x;
			}
			j++;			
		}
//		cout<<(dia+1)/2<<'\n';
		printf("%d\n",(dia+1)/2);
	}
}
	
signed main(){
//    ios::sync_with_stdio(0);
//    cin.tie(0),cout.tie(0);
    int test=1;	
	
	rep(i,2,N-5){
		lg[i]=lg[i>>1]+1;
	}
//    cin>>test;
	test=rd();
    while(test--){	
    	solve();	
    }
}

1012

神秘dp

每次可以选一个区间,把元素变成区间最小值,操作任意次,问能得到多少种不同区间?

考虑每个区间的最值,可能会算重,不如考虑每个元素在哪个区间是最小值,然后每次选一个元素,把最小值是这个元素的区间都操作了。

dp(i,j)dp(i,j)dp(i,j)表示使用了前iii个元素作为变之后的最小值,也就是前i个元素作为最小值的区间都操作过了,前缀长度jjj的序列有多少种不同序列?

转移则是dp(i,j)=∑k=l−1jdp(i−1,k)dp(i,j)=\sum_{k=l-1}^{j} dp(i-1,k)dp(i,j)=k=l1jdp(i1,k)(l,r)(l,r)(l,r)aia_iai作为最小值的区间,考虑我们可以规定[l,k][l,k][l,k]这一段都不变,k=l−1k=l-1k=l1时就是对于aia_iai这个区间不进行任何操作。

iii这个维度可以滚动压掉,因为转移实际上就是前一轮的一个区间和,每轮做个前缀和即可。

void solve()
{
	int n;
	cin>>n;
	vi nums(n+1);
	rep(i,1,n)cin>>nums[i];
	
	vector<int> l(n + 1, 0), r(n + 1, n + 1);
	
	stack<int> s;
	for (int i = 1; i <= n; i++)
	{
	    while (s.size() && nums[i] <= nums[s.top()])
	    {
	        s.pop();
	    }
	    if (s.size())
	        l[i] = s.top();
	    s.push(i);
	}
	
	s = stack<int>();
	for (int i = n; i >= 1; i--)
	{
	    while (s.size() && nums[i] <= nums[s.top()])
	    {
	        s.pop();
	    }
	    if (s.size())
	        r[i] = s.top();
	    s.push(i);
	}	
	
	vi f(n+2),pre(n+1);
	f[0]=1,pre[0]=f[0];
	rep(i,1,n){
		pre[i]=pre[i-1]+f[i];
	}
	rep(i,1,n){
		rep(j,l[i]+1,r[i]-1){
			f[j]+=(pre[j-1]-(l[i]?pre[l[i]-1]:0)+M2)%M2;
			f[j]%=M2;
		}
		rep(j,1,n){
			pre[j]=pre[j-1]+f[j];
			pre[j]%=M2;
//			cout<<f[j]<<' ';
		}
//		cout<<'\n';
	}
	cout<<f[n]<<'\n';
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值