[提高组集训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);
}