“NOIp模拟赛“ 的 “签到T1“

已经无力改题面了。心累了。为什么每次都是校长和狗狗,随便切最难的 T 1 T1 T1 啊……

题目

题目描述
有一个长为 n n n 的序列 a i a_i ai,你需要让其中恰好 k k k a i a_i ai 变为 0 0 0,使得
∑ i = 1 n min ⁡ ( l i , r i ) − a i \sum_{i=1}^{n}\min(l_i,r_i)-a_i i=1nmin(li,ri)ai

为偶数。其中 l i = max ⁡ j = 1 i a j l_i=\max_{j=1}^{i}a_j li=maxj=1iaj,类似的 r i = max ⁡ j = i n a j r_i=\max_{j=i}^{n}a_j ri=maxj=inaj

数据范围与提示
n ⩽ 2.5 × 1 0 4 n\leqslant 2.5\times 10^4 n2.5×104 k ⩽ 25 k\leqslant 25 k25 。显然 1 ⩽ a i ⩽ 1 0 9 1\leqslant a_i\leqslant 10^9 1ai109

思路

看到跟 max ⁡ \max max 有关,第一反应是笛卡尔树。比如树上背包?但是很快你会发现,当 max ⁡ \max max 被设置为 0 0 0 时,两个子树并不独立。所以这种做法很大可能是错误的。

还是从序列的角度思考。我们填好了一个前缀,并不知道后面的情况,不知道 r i r_i ri,就没法计算贡献了。这怎么办?感觉也没法费用提前计算啊。

但是我们注意到,可能的 r i r_i ri 只有 k + 1 k+1 k+1 个,即后缀中的前 k + 1 k+1 k+1 大。有没有可能 提前枚举?也就是说,我们 提前钦定后方的情况,以此计算前面的权值。这想法很疯狂,但确实可以!

f ( i , κ , ℓ , γ , 0 / 1 ) f(i,\kappa,\ell,\gamma,0/1) f(i,κ,,γ,0/1) 为,有 κ \kappa κ a i a_i ai 被设置为 0 0 0,需要满足 l i = ℓ ,    r i + 1 = γ l_i=\ell,\;r_{i+1}=\gamma li=,ri+1=γ 且权值模 2 2 2 0 / 1 0/1 0/1 的方案数。转移就很简单了(应该算简单吧),只需要考虑一下 γ \gamma γ 是否由 a i a_i ai 提供。复杂度 O ( n k 3 ) \mathcal O(nk^3) O(nk3),如果常数小,就可以拿不少的分。

上面这个想法已经很不错了。然而这都不是正解。我们需要像这道题一样,将序列分成两部分,然后合并在一起。因为 全局最大值可以将序列分成两个独立子问题,并且二者的 d p \tt dp dp 可以去掉一维!

f ( i , κ , γ , 0 / 1 ) f(i,\kappa,\gamma,0/1) f(i,κ,γ,0/1) 表示,需要满足 l i ⩾ r i = γ l_i\geqslant r_i=\gamma liri=γ 且权值为 0 / 1 0/1 0/1 的方案数。此时的转移就跟上面的讨论差不多,但是状态数是 O ( n k 2 ) \mathcal O(nk^2) O(nk2) 的!

左右各做一次,枚举全局最大值,直接合并起来。因为全局最大值也是只有 O ( k ) \mathcal O(k) O(k) 个,合并的复杂度是每次 O ( k 2 ) \mathcal O(k^2) O(k2) 的,最坏也就是 O ( n k 2 ) \mathcal O(nk^2) O(nk2) 了。

代码

#pragma GCC optimize(2)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cassert>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MAXN = 25005;
const int MAXK = 27;
int a[MAXN], n, k;
void push_back(int* l,int* r,int v){
	if(v <= *r) return ;
	for(*r=v; l!=r; --r)
		if(*(r-1) < *r)
			swap(*(r-1),*r);
		else break;
}
void best_unique(int* l,int* r){
	for(int *p=++l; p!=r; ++p)
		if((*l = *p) != *(l-1))
			++ l; // try next one
	memset(l,-1,(r-l)<<2);
}


const int Mod = 1e9+7;
inline void add(int &x,const int &y){
	(x += y) >= Mod ? (x -= Mod) : 0;
}

int pos[MAXK]; // temporary helper
void solve(int pre[][MAXK],int dp[][MAXK][MAXK][2]){
	memset(pre[0]+1,-1,k<<2); // invalid
	pre[0][0] = 0; // 0 is acceptable
	rep(i,1,n){
		memcpy(pre[i],pre[i-1],(k+1)<<2);
		push_back(pre[i],pre[i]+k,a[i]);
	}
	dp[0][0][0][0] = 1;
	rep(i,1,n){
		best_unique(pre[i],pre[i]+k+1);
		pre[i][k+1] = -1; // for convenience
		int posa = k+1; // fail
		for(int j=0,t=0; j<=k; ++j){
			while(pre[i][t] > pre[i-1][j])
				++ t; // find match
			if(pre[i][t] == pre[i-1][j])
				pos[j] = t; // found
			else pos[j] = k+1; // fail
			if(pre[i][j] == a[i]) posa = j;
		}
		rep(cnt,0,k) rep(j,0,cnt) rep(d,0,1){
			// meanwhile ensure pre[i-1][j] != -1
			if(!dp[i-1][cnt][j][d]) continue;
			/* if this is deleted */ ;
			if(cnt != k){
				int v = pre[i-1][j]&1;
				add(dp[i][cnt+1][pos[j]][d^v],
					dp[i-1][cnt][j][d]);
			}
			/* if this is not deleted */ ;
			if(a[i] <= pre[i-1][j]){
				int v = (pre[i-1][j]^a[i])&1;
				add(dp[i][cnt][pos[j]][d^v],
					dp[i-1][cnt][j][d]);
			}
			else add(dp[i][cnt][posa][d],
					dp[i-1][cnt][j][d]);
		}
	}
}

int dp0[MAXN][MAXK][MAXK][2]; // count, prefix max, val
int dp1[MAXN][MAXK][MAXK][2];
int pre[MAXN][MAXK], suf[MAXN][MAXK];
int main(){
	n = readint(), k = readint();
	rep(i,1,n) a[i] = readint();
	solve(suf,dp1); // backward
	reverse(a+1,a+n+1);
	solve(pre,dp0); // forward
	int ans = 0, tmp[2], ppl[2];
	for(int *v=pre[n]; *v!=-1; ++v)
		rep(i,1,n) if(a[i] == *v){
			rep(cnt,0,k){ // count of prefix
				rep(j,tmp[0]=tmp[1]=0,cnt){
					if(pre[i-1][j] == -1) break;
					if(pre[i-1][j] >= *v) continue;
					rep(d,0,1) add(tmp[d],
						dp0[i-1][cnt][j][d]);
				}
				int (*ddg)[2] = dp1[n-i][k-cnt];
				rep(j,ppl[0]=ppl[1]=0,k-cnt){
					if(suf[n-i][j] == -1) break;
					if(suf[n-i][j] > *v) continue;
					rep(d,0,1) add(ppl[d],ddg[j][d]);
				}
				ans = (ans+int_(ppl[0])*tmp[0]
					+int_(ppl[1])*tmp[1])%Mod;
			}
		}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值