AtCoder-abc265_f Manhattan Cafe
Manhattan Cafe
dp 前缀和优化
很容易想到 \(dp\) 的状态
\(dp[i][j][k]\) 表示前 \(i\) 个点,\(r_x\) 与 \(p_x\) 的差值和为 \(j\),\(r_x\) 与 \(q_x\) 的差值和为 \(k\)
这样的话直接枚举第 \(i\) 组点中,我们取的点的位置,能够做到 \(O(D)\) 的复杂度转移
总的时间复杂度为 \(O(nD^3)\),显然超时
考虑转移的时间进行优化,我们会发现选取的位置不同,会有不同的规律,假设 \(p_i = 1\),\(q_i = 4\)
如果选取的点 \(r_i\) 在 \([1, 4]\),那么 \(|p_i-x_i|+|q_i-x_i| = |p_i - q_i|\),也就是 \(dp[i][j][k] = \sum dp[i-1][j-x][k-y],\) \(x+y=|p_i - q_i|\),此时一定是从一个 \(j'+k'\) 为定值的之前的状态转移过来,这个定值就是 \(j+k-|p_i-q_i|\)
如果选取的点在 \([1, 4]\) 之外,那么与上一个转移相反,这种是从满足 \(j'-k'\) 为定值的状态转移过来,这个定值就是 \(|p_i-q_i|\)
上述转移文字可能不清楚,可以自己列表格,对每个离散点判断一下他应该从什么状态转移过来,就可以理解
因此考虑做两个对角线前缀和,每次 \(O(1)\) 转移
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1e3 + 10;
typedef long long ll;
const ll mod = 998244353;
ll dp[maxn][maxn], dig[maxn][maxn], a_dig[maxn][maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, d;
cin >> n >> d;
vector<int>q(n), p(n);
for(int i=0; i<n; i++) cin >> q[i];
for(int i=0; i<n; i++) cin >> p[i];
dp[0][0] = 1;
for(int i=0; i<n; i++)
{
for(int j=0; j<=d; j++)
{
for(int k=0; k<=d; k++)
{
dig[j][k] = a_dig[j][k] = dp[j][k];
if(j && k < d)
dig[j][k] += dig[j - 1][k + 1];
if(j && k)
a_dig[j][k] += a_dig[j - 1][k - 1];
}
}
for(int j=0; j<=d; j++)
{
for(int k=0; k<=d; k++)
{
int s = abs(p[i] - q[i]);
dp[j][k] = 0;
if(k + j >= s)
{
int l = max(0, j - s), r = k + j - s - max(0, k - s);
if(l == 0) dp[j][k] += dig[r][k - s + j - r];
else dp[j][k] += dig[r][k - s + j - r] - dig[l - 1][k - s - l + j + 1];
}
if(k > s && j)
dp[j][k] += a_dig[j - 1][k - 1 - s];
if(j > s && k)
dp[j][k] += a_dig[j - 1 - s][k - 1];
dp[j][k] %= mod;
}
}
}
ll ans = 0;
for(int i=0; i<=d; i++)
for(int j=0; j<=d; j++)
ans += dp[i][j];
cout << ans % mod << endl;
return 0;
}