CF1561
Codeforces Round #740 (Div. 2, based on VK Cup 2021 - Final (Engine))
D - Up the Strip
题意
给出一个数字 表示初始的数字,你可以对当前的数字(比如说是 )做若干次变化,变化包含下列两种:
-
选择一个数字 ,将现在的数字 变为 。
-
选择一个数字 ,将现在的数字 变为 。
求将初始数字 变为 的不同的操作有多少种?
答案对 取模,每次会给出 。
数据范围:。
思路
求不同操作的个数,可以用dp完成,令 为将数字 转变为 的不同操作个数,则转移为:
右侧第一个式子可以使用数论分块在 内完成,然后从1到n计算对应的 ,右侧用前缀和即可 求出。
总复杂度:,于是 Up the Strip (simplified version) 就可以通过了。
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
const int N = 2e5 + 10;
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int n, m, sum = 0;
cin >> n >> m;
vi f(n+1);
f[1] = 1;
sum = 1;
for (int i = 2; i <= n; i++) {
for (int l = 2, r; l <= i; l = r + 1) {
r = i / (i / l);
f[i] = (f[i] + f[i/l] * (r - l + 1) % m) % m;
}
f[i] = (f[i] + sum) % m;
sum = (sum + f[i]) % m;
}
cout << f[n] << '\n';
return 0;
}
如果要通过 的数据,时间复杂度不能超过 ,空间复杂度不能超过 (128Mb)。
考虑 中相邻两项之间的关系,下面列式观察:
不难发现 和 中第一项只有分子发生了变化,从 变为 。
那么只需要考虑,分母在何时会发生变化即可。
当 时,那么只有当 时, 和 才会发生变化
而且只是从 变为 。
所以 从 的转移可以写成:
由于 ,所以如果我们直接预处理能整除 的 时,在空间上会爆掉的。
所以正确的做法有如下两种:
他们时空间复杂度都一样为:时间复杂度 ,空间复杂度 。
法一:
对每个 求它所有的因数(需要先用筛法求出素数表,然后求 对应的素因数,最后暴力枚举每个素数的个数)。
#include <bits/stdc++.h>
#define db double
#define ll long long
//#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 4e6 + 10;
int num[N];
bool vis[N];
vi fac, prims;
vp prim;
void Euler(int n) {
for (int i = 2; i <= n; i++) {
if (!vis[i]) prims.pb(i);
for (auto j : prims) {
int t = i * j;
if (t > n) break;
vis[t] = 1;
if (i % j == 0) break;
}
}
}
void dfs(int now, int mul) {
if (now == prim.size()) {
fac.pb(mul);
return;
}
dfs(now+1, mul);
for (int i = 0; i < prim[now].second; i++) {
mul *= prim[now].first;
dfs(now+1, mul);
}
}
void makefac(int n) {
fac.clear();
prim.clear();
for (auto i : prims) {
if (i * i > n) break;
if (n % i == 0) {
int cnt = 0;
while (n % i == 0) {
cnt++;
n /= i;
}
prim.pb({i, cnt});
}
}
if (n != 1) prim.pb({n, 1});
dfs(0, 1);
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int n, P;
cin >> n >> P;
Euler(n);
cout << prims.size() << '\n';
num[n] = 1;
for (int i = n; i >= 3; i--) {
num[i-1] = (num[i-1] + 2 * num[i] % P) % P;
num[1] = (num[1] + num[i]) % P;
makefac(i);
for (auto j : fac) {
if (j == 1 || j == i) continue;
num[i/j] = (num[i/j] + num[i]) % P;
num[i/j-1] = (num[i/j-1] - num[i] + P) % P;
}
}
cout << (num[2] * 2 % P + num[1]) % P << '\n';
return 0;
}
法二(更优美的做法):
就是考虑倒序求解,从 求解,考虑每个数 对 的贡献。
#include <bits/stdc++.h>
#define db double
#define ll long long
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 4e6 + 10;
int f[N];
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int n, P;
cin >> n >> P;
f[1] = 1;
for (int i = 2; i <= n; i++ ) {
f[i] = (f[i] + 2 * f[i-1] % P + (i != 2 ? f[1] : 0)) % P;
for (int j = 2; j * i <= n; j++) {
f[j*i] = ((f[j*i] + f[i]) % P - f[i-1] + P) % P;
}
}
cout << f[n] << '\n';
return 0;
}
E - Bottom-Tier Reversals
题意
给出 到 的排列, ,其中 为奇数。
你可以进行一次操作,每次操作可以选择当前序列的前 项,必须为任意的奇数,然后反转他们,也就是将 和 交换位置。
如果你可以在不超过 次操作后完成当前序列的排序,先输出反转的次数,再输出每次反转的长度。
如果不能则输出 。
数据范围:。
思路
性质:如果每次反转的区间长度为奇数,那么反转元素的下标的奇偶性不发生变化,如 ,奇偶性不变。
那么由于每次反转只能反转奇数长度,所以一定有 与 的奇偶性相同,也就是 。
下面用具体操作证明这是一个充要条件。
由于又限制了每次反转只能在开头,开头位置无法改变,但末尾位置如果已经对应好了,则可以改变,所以考虑当前的末尾元素是否满足条件:
-
若 ,则不需要交换,继续考虑 。
-
若 或 ,则考虑下面5次交换,使得 :
-
设 (注: 为奇数),反转 。
-
设 (注: 为偶数),反转 。
-
设 ,反转 。
-
反转 。
-
反转 。
上面步骤可以在草稿纸上模拟完成,便于理解。
于是总操作步骤一定不会超过 步。
只需模拟上述操作即可,时间复杂度 。
#include <bits/stdc++.h>
#define db double
#define ll long long
#define int ll
#define vi vector<int>
#define vii vector<vi >
#define pii pair<int, int>
#define vp vector<pii >
#define vip vector<vp >
#define mkp make_pair
#define pb push_back
#define Case(x) cout << "Case #" << x << ": "
using namespace std;
const int INF = 0x3f3f3f3f;
const int P = 998244353;
void solve() {
int n;
cin >> n;
vi a(n+1), ans;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
if (a[i-1] % 2 == 0 && i % 2 == 1) {
cout << -1 << '\n';
return;
}
}
for (int i = n; i >= 2; i -= 2) {
if (a[i-1] == i && a[i-2] == i-1) continue;
int tmp = find(a.begin(), a.end(), i) - a.begin() + 1;
ans.pb(tmp);
reverse(a.begin(), a.begin() + tmp);
tmp = find(a.begin(), a.end(), i-1) - a.begin();
ans.pb(tmp);
reverse(a.begin(), a.begin() + tmp);
ans.pb(tmp + 2);
reverse(a.begin(), a.begin() + tmp + 2);
ans.pb(3);
reverse(a.begin(), a.begin() + 3);
ans.pb(i);
reverse(a.begin(), a.begin() + i);
}
cout << ans.size() << '\n';
for (auto i : ans) cout << i << ' ';
cout << '\n';
}
signed main(){
#ifdef _DEBUG
// FILE *file = freopen("out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}