[提高组集训2021] 雨怎么越下越大了😓

一、题目

雨怎么越下越大了😓

宋队有 \(n\) 个马桶(虽然实际上还有一个马桶),第 \(i\) 个的高度为 \(a_i\)因为雨怎么越下越大了,这些马桶中产生了积水,积水符合物理定律,并且你可以假设雨下的足够大。

宋队有把马桶高度变成 \(0\) 的魔法(?),她想知道使用魔法 \(k\) 次中有多少种方案使得积水的体积是偶数。

\(n\leq 25000,h\leq 10^9,k\leq \min(25,n-1)\)

二、解法

首先考虑暴力怎么写,搜出所有情况后,第 \(i\) 个点的积水是 \(\min(pre_{i-1},suf_{i+1})-a_i\)

如果我们从左到右 \(dp\),那么我们最多只能知道前缀最大值,但是计算代价需要后缀最大值啊。这时候可以使用假设法,第一种思路是把假设的后缀最大值也记录下来,转移的时候注意不产生矛盾即可,但不优美;第二种方法是假设后缀最大值大于等于前缀最大值,这就利用了代价 \(\min\) 的特性。

\(f[i][j][k][0/1]\) 表示处理前 \(i\) 个点,最大值是 \(j\),已经使用的魔法个数是 \(k\),现在积水的奇偶性是偶数\(/\)奇数,\(g[i][j][k][0/1]\) 表示处理后 \(i\) 个点的状态,两者转移方式类似。

因为魔法只有 \(k\) 次,所以每个位置的最大值最多 \(k+1\) 种,可以预处理一个数组把所有可能的最大值重标号,转移的时候暴力对应一下即可,时间复杂度 \(O(nk^2)\)

最后的问题是解决假设,我们枚举全局最大值所在的位置,然后用前缀和后缀处理出来的 \(dp\) 值就可以算答案了,因为全局最大值也最多只有 \(k+1\) 个,所以多少循环来合并都行,注意处理多个最大值的情况,我们枚举第一个最大值,然后强制前缀的最大值要小于当前枚举的最大值即可。

三、总结

本题的难点是代价计算,方法有费用提前计算\(/\)费用延后计算,如果需要当前立即算出代价,要学会给自己创造信息。暴力记录假设的值可行,但是巧妙利用代价的特性来假设更值得借鉴。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 25005;
const int MOD = 1e9+7;
#define pii pair<int,int>
#define mp make_pair
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],b1[M][30],b2[M][30],l1[M],l2[M],p[M];
int ans,f[M][30][30][2],g[M][30][30][2];set<pii> s;
void add(int &x,int y) {x=(x+y)%MOD;}
void work1(int i,int j)
{
	int to=0;
	for(int k=0;k<=l1[i];k++)
		if(b1[i-1][j]==b1[i][k]) to=k;
	for(int k=0;k<=m;k++) for(int w=0;w<2;w++)
	{
		if(k<m)//fuck i
		{
			int c=(b1[i][to]+w)%2;
			add(f[i][to][k+1][c],f[i-1][j][k][w]);
		}
		if(a[i]>b1[i][to])
			add(f[i][p[i]][k][w],f[i-1][j][k][w]);
		else
		{
			int c=(b1[i][to]-a[i]+w)%2;
			add(f[i][to][k][c],f[i-1][j][k][w]);
		}
	}
}
void work2(int i,int j)
{
	int to=0;
	for(int k=0;k<=l2[i];k++)
		if(b2[i+1][j]==b2[i][k]) to=k;
	for(int k=0;k<=m;k++) for(int w=0;w<2;w++)
	{
		if(k<m)//fuck i
		{
			int c=(b2[i][to]+w)%2;
			add(g[i][to][k+1][c],g[i+1][j][k][w]);
		}
		if(a[i]>b2[i][to])
			add(g[i][p[i]][k][w],g[i+1][j][k][w]);
		else
		{
			int c=(b2[i][to]-a[i]+w)%2;
			add(g[i][to][k][c],g[i+1][j][k][w]);
		}
	}
}
int main()
{
	freopen("rain.in","r",stdin);
	freopen("rain.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();s.insert(mp(a[i],i));
		if(s.size()>m+1) s.erase(s.begin());
		for(auto x:s)
		{
			b1[i][++l1[i]]=x.first;
			if(x.first==a[i]) p[i]=l1[i];
		}
	}
	f[0][0][0][0]=g[n+1][0][0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=l1[i-1];j++)
			work1(i,j);
	s.clear();
	for(int i=n;i>=1;i--)
	{
		s.insert(mp(a[i],-i));
		if(s.size()>m+1) s.erase(s.begin());
		for(auto x:s)
		{
			b2[i][++l2[i]]=x.first;
			if(x.first==a[i]) p[i]=l2[i];
		}
	}
	for(int i=n;i>=1;i--)
		for(int j=0;j<=l2[i+1];j++)
			work2(i,j);
	for(auto x:s)
	{
		int h=x.first,i=-x.second;
		for(int a=0;a<=l1[i-1];a++) if(b1[i-1][a]<h)
		for(int b=0;b<=l2[i+1];b++) if(b2[i+1][b]<=h)
		for(int c=0;c<=m;c++)
		{
			add(ans,1ll*f[i-1][a][c][0]*g[i+1][b][m-c][0]%MOD);
			add(ans,1ll*f[i-1][a][c][1]*g[i+1][b][m-c][1]%MOD);
		}
	}
	printf("%d\n",ans);
}
posted @ 2021-10-06 19:06  C202044zxy  阅读(117)  评论(0编辑  收藏  举报