题意:
给定一个2*m的格子,起点是(1,1),要求遍历完其他格子,且每个格子只能走一次。每个格子(i, j)有设定进入时间a[i][j],表示在时刻a[i][j]之前,不能访问该格子。
问按要求,遍历完所有格子,且满足上述条件的情况下,最少需要多少时间。
题解:
因为既要遍历所有的格子又要不重复,所以路线是很有限的,除了从起点开始就一直走蛇形路线,即一直下右上右循环外,当不按照这种方式走的时候,为了满足条件,行径路线就会变成唯一,即只能走从该点走到最右端再从另一行的最右端返回的这种U型路线。
首先维护一个数组f[i][j],表示从i行j列开始向右流畅走U型路线的最小值,流畅走就意味着当你走到点的时候无需等待格子解锁,即贪心策略就是先在原地等待一段时间,此后保证走到哪个点,哪个点一定是解锁状态。
转移方程如下:
f[i][j] = max(f[i][j + 1] - 1, max(a[i ^ 1][j] - 2 * (n - j) - 1, a[i][j]));
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <vector>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <bitset>
#include <unordered_map>
#include <set>
#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
const int N = 200010, mod = 1000000007, INF = 1e9;
typedef long long LL;
typedef pair<int, int> PII;
int n, T;
int x;
int f[2][N];
int a[2][N];
void solve()
{
memset(f, 0, sizeof f);
cin >> n;
for (int i = 0; i < 2; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> a[i][j];
a[0][1] = -1;
//无需特判第一个点是否需要等待,然后来判断初始化res是加 2n-1 or 2n
f[0][n] = max(a[0][n], a[1][n] - 1);
f[1][n] = max(a[1][n], a[0][n] - 1);
for (int i = 0; i < 2; i ++ )
for (int j = n - 1; j >= 1; j -- )
f[i][j] = max(f[i][j + 1] - 1, max(a[i ^ 1][j] - 2 * (n - j) - 1, a[i][j]));
// cout << f[0][1] << endl;
LL res = f[0][1] + 2 * n, rec = a[1][1] + 1;
for (int col = 2, row = 1; col <= n; col ++ , row ^= 1)
{
res = min(res, rec + max((LL)f[row][col] - rec, (LL)0) + 2 * (n - col + 1));
rec = max(rec + 1, (LL)a[row][col] + 1);
rec = max(rec + 1, (LL)a[row ^ 1][col] + 1);
}
res = min(res, rec);
cout << res << endl;
}
int main()
{
quick_cin();
cin >> T;
// T = 1;
while (T -- )
{
solve();
}
return 0;
}
题意:
在一维坐标中,从 0 位置开始跳。
第 1 次移动可以走 k 的倍数距离,第 2 次移动可以走 k+1 的倍数距离,…,第 m 次跳可以走 k+m-1 的倍数距离。
问,跳到 1~n 位置的方案数分别为多少?
题解:
Part1:dp 状态设计与暴力 dp。
首先将步数和目前到达的位置设为状态,即表示走了
步之后到达
的方案数。
此时临界状态。
遍历所有位置 ,遍历跳的次数
,遍历上一次跳的倍数
,所以转移方程为:
f[i][j] += f[i - b * (k + j - 1)][j - 1];
但是此时需要我们枚举步数,枚举目前所在的下标,还要枚举出发点,总的时间复杂度为 ,不能通过本题。
Part 2:缩小值域优化时空复杂度。
设 m 为 chip 可以走的步数,则有下式:
,
当 时 m 可以取最大值。我们发现 m 的最大值不会超过
,也就是说,我们可以把步数的值域缩小到根号级别的,这样时间复杂度就会达到
,还是无法通过本题。
Part 3:利用前缀和优化时间复杂度。
考虑把原先的状态转移方程拆开:
引入:
于是联立得到:
f[i][j] = f[i - (k + j - 1)][j] + f[i - (k + j - 1)][j - 1];
此时时间复杂度降到了,但此时空间复杂度是
,依旧不行。
Part 4:利用滚动数组优化空间复杂度。
然而这一道题还没有做完。的空间复杂度是我们接受不了的,观察发现第
步的状态只和第
步有关,所以我们可以利用滚动数组滚掉一维,空间复杂度便能降到
,可以通过本题。
具体代码如下:
#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#include <set>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define quick_cin() cin.tie(0),ios::sync_with_stdio(false)
#define endl "\n"
#define pb push_back
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 200010, mod = 998244353, INF = 1e18;
int T;
LL n, m, k;
int f[N][2];
LL ans[N];
void solve()
{
cin >> n >> k;
f[0][0] = 1;
for (int j = 1; j * j <= 2 * n; j ++ )
{
for (int i = 1; i <= n; i ++ )
if (i >= k + j - 1)
f[i][j % 2] = (f[i - (k + j - 1)][j % 2] + f[i - (k + j - 1)][(j % 2) ^ 1]) % mod;
for (int i = 0; i <= n; i ++ )
{
ans[i] = (ans[i] + f[i][j % 2]) % mod;
f[i][(j % 2) ^ 1] = 0;
}
}
for (int i = 1; i <= n; i ++ ) cout << ans[i] << " ";
}
signed main()
{
// quick_cin();
// cin >> T;
T = 1;
while (T -- )
{
solve();
}
}