二维积水(DP优化)

题面

在二向箔爆发前的时间里,宇宙中就有一个叫地球的星球,上面存在过奴隶主,后来绝迹了……

——《第三维的往事》

在这个美丽的二维宇宙中,有一个行星叫地圆。地圆有一条大陆叫美洲,上面生活着一个奴隶主,叫 S Y SY SY ,经营着数不清的☻奴隶在一条长为 N   m N~\tt m N m 的种植园上劳作。

奴隶们苦不堪言,因为这条种植园地势崎岖。具体地说,从左到右第 i i i 块长为 1   m 1~\tt m 1 m 的地高度为(正整数) h i   m h_i ~\tt m hi m ,并且种植园左边和右边的广阔荒地出奇的平坦和低洼,它们的高度都是 0 0 0

另外,奴隶们还很怕下雨,因为雨只会在种植园上空下,一旦下雨,就会积水(水面线是平的,积水原则和实际相符)。而且每次的雨都足够大,会下到积水量不再增加为止。

// Sample #1, widened
..............
..........##..
..##~~~~~~##..
..##~~##~~##..
..##~~##~~##..
########~~####
##############

S Y SY SY 在每次下雨后都会统计一次棉花产出,他发现,如果雨停后残留的积水总量(单位: m 2 \tt m^2 m2)为偶数,那么棉花的产出会非常高。

于是 S Y SY SY 决定发动奴隶们铲地,把刚好 K K K 块长为 1   m 1~\tt m 1 m 的地高度铲为 0 0 0 ,使得下一次来雨后,积水的总量为偶数。

由于二维宇宙的电子计算机太慢了,于是他把数据发给了三维宇宙的你,询问总共 ( N K ) {N\choose K} (KN) 种方案中,有多少种铲地方案可以达到他的要求。答案对 1 0 9 + 7 \tt 10^9+7 109+7 取模。

输入

第一行两个整数 N , K N,K N,K

第二行一个长为 N N N 的正整数数列 h h h

每行的数字之间以空格分隔。

1 ≤ N ≤ 25000 , 1 ≤ h i ≤ 1 0 9 , 1 ≤ K ≤ min ⁡ ( 25 , N − 1 ) 1\leq N\leq25000,1\leq h_i\leq 10^9,1\leq K\leq \min(25,N-1) 1N25000,1hi109,1Kmin(25,N1).

输出

一个取模后的整数表示答案。

样例

Input #1

7 1
2 5 2 4 1 6 2

Output #1

4

Input #2

18 8
6 3 2 2 2 1 5 1 2 2 5 5 5 4 6 4 4 1

Output #2

21931

题解

我们设简单一点的 D P DP DP 状态,令 d p l [ i ] [ j ] dpl[i][j] dpl[i][j] 为前 i − 1 i-1 i1 块地中选了 j j j 块地铲掉,第 i i i 块地在保留地中最高(允许前面有和 i i i 等高的保留地) 的奇/偶方案数。没错,他是个二元组 ( 奇 方 案 , 偶 方 案 ) (奇方案,偶方案) (,)。这种二元组的乘法定义为 ( a , b ) × ( c , d ) = ( a c + b d , a d + b c ) (a,b)\times(c,d)=(ac+bd,ad+bc) (a,b)×(c,d)=(ac+bd,ad+bc) ,即两个不相干的地区的方案合并。定义加法为 ( a , b ) + ( c , d ) = ( a + c , b + d ) (a,b)+(c,d)=(a+c,b+d) (a,b)+(c,d)=(a+c,b+d) ,即同一性质的方案数累加。接下来就好做了。

转移比较恶心,感性理解吧:枚举 i i i 前面第二高的地 y y y可以与 i i i 等高),从 d p l [ y ] [ j − c n t ] dpl[y][j-cnt] dpl[y][jcnt] 转移到 d p l [ i ] [ j ] dpl[i][j] dpl[i][j] ,其中 c n t cnt cnt 为区间 ( y , i ) (y,i) (y,i) 内高度大于等于 h y h_y hy 的土地个数,这些土地都是必须要铲的。再算上区间 ( y , i ) (y,i) (y,i) 内自由地的铲除方案,合在 d p l [ y ] [ i − c n t ] dpl[y][i-cnt] dpl[y][icnt] 里面一并转移过来。把 i i i 算好后,动态维护前面土地的转移数组,若 h y > h i h_y>h_i hy>hi,那么接下来的计算中, i i i 都算作 y y y 转移时的自由地,于是把 i i i 铲与不铲的方案算进 d p l [ y ] dpl[y] dpl[y] 中作为“转移用 d p l [ y ] dpl[y] dpl[y]”,后面的土地再计算好后,同样地看情况算进“转移用 d p l dpl dpl”数组中。我们会发现,每时每刻可以用来转移的 y y y 最多只有 K K K 个左右,往往是最高的 K K K 个(相等的就是最靠右的优先)。因此复杂度 O ( N K 2 ) O(NK^2) O(NK2)

我们还得计算类似的 d p r [ i ] [ j ] dpr[i][j] dpr[i][j] ,表示在第 i i i 块地右边的地选 j j j 块铲掉,不过此时不允许后面有高度大于等于 i i i 的保留地。而且转移中的所有细节取等与否是相反的(详见上方标黑,转移地不可与 i i i 等高,大于等于变成大于,但是等高的转移地仍然是最靠右的优先)。

最后我们把每个 d p l [ i ] [ j ] × d p r [ i ] [ K − j ] dpl[i][j]\times dpr[i][K-j] dpl[i][j]×dpr[i][Kj] 都贡献给答案。这相当于枚举了铲地后最高的那块地(等高的地以右为尊)。

CODE

唉,算了,如果没看懂可以看代码或者自己想。

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 25005
#define LL long long
#define ULL unsigned long long
#define UI unsigned int
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define eps (1e-4)
#define MI map<LL,int>::iterator
#pragma GCC optimize(2)
int xchar() {
	static const int SZ = 1000005;
	static char ss[SZ];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(ss,1,SZ,stdin);
	if(pos == len) return -1;
	return ss[pos ++];
}
LL xread() {
	LL f=1,x=0;int s = xchar();
    while(s < '0' || s > '9') {if(s=='-')f = -f;s = xchar();}
    while(s >= '0' && s <= '9') {x=x*10+(s^48);s = xchar();}
    return f*x;
}
LL read() {
    LL f=1,x=0;char s = getchar();
    while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
    while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
    return f*x;
}
void putpos(LL x) {
    if(!x) return ;
    putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
    if(!x) putchar('0');
    else if(x < 0) putchar('-'),putpos(-x);
    else putpos(x);
}
void AIput(LL x,char c) {putnum(x);putchar(c);}

const int MOD = 1000000007;
int n,m,s,o,k;
int h[MAXN];
struct it{
	int n0,n1;
	it(){n0=n1=0;}
	it(int ev,int od){n0=ev;n1=od;}
};
it& operator += (it &a,it b) {
	(a.n0 += b.n0) %= MOD;
	(a.n1 += b.n1) %= MOD;
	return a;
}
it operator + (it a,it b) {
	(a.n0 += b.n0) %= MOD;
	(a.n1 += b.n1) %= MOD;
	return a;
}
it operator * (it a,it b) {
	it c;c.n0 = (a.n1*1ll*b.n1%MOD + a.n0*1ll*b.n0%MOD)%MOD;
	c.n1 = (a.n1*1ll*b.n0%MOD + a.n0*1ll*b.n1%MOD)%MOD;
	return c;
}
set<pair<int,int> > st;
it dp[MAXN][30];
it fl[MAXN][30],fr[MAXN][30];
map<int,int> mp;
vector<int> bu[MAXN];
int nm;
struct tr{
	int ls,rs;
	LL nm; int ct;
	tr(){ls=rs=nm=ct=0;}
}tre[MAXN*42];
int CNT = 0;
int addtree(int a,int x,int al,int ar,int y) {
	if(al > x || ar < x) return a;
	tre[++CNT] = tre[a]; a = CNT;
	tre[a].nm += y; tre[a].ct ++;
	if(al == ar) return a;
	int md = (al + ar) >> 1;
	tre[a].ls = addtree(tre[a].ls,x,al,md,y);
	tre[a].rs = addtree(tre[a].rs,x,md+1,ar,y);
	return a;
}
void gettree(int a,int l,int r,int al,int ar,int &c,LL &y) {
	if(l > r || al > r || ar < l || !a) return ;
	if(al >= l && ar <= r) {c += tre[a].ct,y += tre[a].nm;return ;}
	int md = (al + ar) >> 1;
	gettree(tre[a].ls,l,r,al,md,c,y);
	gettree(tre[a].rs,l,r,md+1,ar,c,y);
	return ;
}
int rt[MAXN];
LL su[MAXN];
int cnc[MAXN];
int main() {
	freopen("rain.in","r",stdin);
	freopen("rain.out","w",stdout);
	n = read();m = read();
	for(int i = 1;i <= n;i ++) {
		h[i] = read();
		su[i] = su[i-1] + h[i];
		mp[h[i]] = 1;
	}
	for(auto i = mp.begin();i != mp.end();i ++) {i->SE = ++ nm;}
	for(int i = 1;i <= n;i ++) bu[mp[h[i]]].push_back(i);
	for(int i = nm;i > 0;i --) {
		rt[i] = rt[i+1];
		for(int j = 0;j < (int)bu[i].size();j ++) {
			rt[i] = addtree(rt[i],bu[i][j],1,n,h[bu[i][j]]);
		}
	}
	rt[0] = rt[1];
	st.insert(make_pair(0,0));
	dp[0][0].n0 = 1;
	for(int i = 1;i <= n;i ++) {
		int cn = 0;
		for(auto j = st.begin();j != st.end() && cn <= m;j ++) {
			int y = -j->SE;
			if(h[y] > h[i]) {cn ++;continue;}
			int cc = 0; LL qu = 0;
			qu = 0;
			gettree(rt[mp[h[y]]],y+1,i-1,1,n,cc,qu);
			cn = cnc[y];
			if(cn > m) break;
			LL sm = h[y]*1ll*(i-y-1) - (su[i-1]-su[y]-qu);
			for(int k = cc;k <= m;k ++) {
				it tm = dp[y][k - cc];
				if(sm & 1) swap(tm.n0,tm.n1);
				dp[i][k] += tm;
			}
		}
		memcpy(fl[i],dp[i],sizeof(fl[i]));
		cn = 0;
		it my = it(h[i]%2 == 0,h[i]%2);
		for(auto j = st.begin();j != st.end() && cn <= m;j ++,cn ++) {
			int y = -j->SE;
			if(cnc[y] > m) break;
			if(h[y] > h[i]) {
				for(int k = m;k > 0;k --) {
					dp[y][k] += dp[y][k-1]*my;
				}
				cnc[i] ++;
			}
			else cnc[y] ++;
		}
		st.insert(make_pair(-h[i],-i));
	}
	st.clear();
	st.insert(make_pair(0,-n-1));
	dp[n+1][0].n0 = 1;
	cnc[n+1] = 0;
	for(int i = 1;i <= n;i ++) {
		cnc[i] = 0;
		for(int j = 0;j <= m;j ++) dp[i][j] = it();
	}
	for(int i = n;i > 0;i --) {
		int cn = 0;
		for(auto j = st.begin();j != st.end() && cn <= m;j ++) {
			int y = -j->SE;
			if(h[y] >= h[i]) {cn ++;continue;}
			int cc = 0; LL qu = 0;
			gettree(rt[mp[h[y]]+1],i+1,y-1,1,n,cc,qu);
			cn = cnc[y];
			if(cn > m) break;
			LL sm = h[y]*1ll*(y-i-1) - (su[y-1]-su[i]-qu);
			for(int k = cc;k <= m;k ++) {
				it tm = dp[y][k - cc];
				if(sm & 1) swap(tm.n0,tm.n1);
				dp[i][k] += tm;
			}
		}
		memcpy(fr[i],dp[i],sizeof(fr[i]));
		cn = 0;
		it my = it(h[i]%2 == 0,h[i]%2);
		for(auto j = st.begin();j != st.end() && cn <= m;j ++) {
			int y = -j->SE;
			if(cnc[y] > m) break;
			if(h[y] >= h[i]) {
				for(int k = m;k > 0;k --) {
					dp[y][k] += dp[y][k-1]*my;
				}
				cnc[i] ++;
			}
			else cnc[y] ++;
		}
		st.insert(make_pair(-h[i],-i));
	}
	int ans = 0;
	for(int i = 1;i <= n;i ++) {
		for(int j = 0;j <= m;j ++) {
			it nw = fl[i][j] * fr[i][m-j];
			(ans += nw.n0) %= MOD;
		}
	}
	if(m == n) (ans += 1) %= MOD;
	AIput(ans,'\n');
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值