7.14 海高集训 DP 专题 2
出题人:\(\text{D}\color{red}\text{eaphetS}\)
#A. [NOIP2012 提高组] 开车旅行
倍增优化 dp。
这题难就难在预处理。
首先预处理出 A 和 B 每个人从一个城市出发的目标是哪个城市。可以用平衡树找一个点的前驱和后继,或者双向链表。我当然选择了最偷懒的 set。(ps:这里如果用 set 的话有可能迭代器一直加或者减导致越界,又懒得判断,索性用了 multiset)
然后预处理出 \(f[i][j][k(0/1)]\) 表示第 \(k\) 个人从第 i 个城市出发走 \(2^j\) 天到达的城市,同时也更新 \(dp[x(0/1)][i][j][k(0/1)]\) 表示第 \(k\) 个人从第 \(i\) 个城市出发走 \(2^j\) 天后第 \(x\) 个人走了多少路程。
最后预处理完暴力跑几遍就行了。
#include <cstdio>
#include <algorithm>
#include <set>
using std::set;
int n, m, s, h[100005], des[100005][2], Min[100005][2], To[100005][22], dis[100005][22][2], tot[2], x;
struct info {
int h, id;
bool operator < (const info a) const {
return h < a.h;
};
};
set<info>box;
set<info>::iterator I;
int ab(int t) {
return t < 0 ? -t : t;
}
void consider(int i, info p) {
int j = p.id;
if ((ab(h[i] - h[j]) < Min[i][0]) || (Min[i][0] == ab(h[i] - h[j]) && h[j] < h[des[i][0]])) {
if ((Min[i][0] < Min[i][1]) || (Min[i][1] == Min[i][0] && h[des[i][0]] < h[des[i][1]]))
Min[i][1] = Min[i][0], des[i][1] = des[i][0];
Min[i][0] = ab(h[i] - h[j]), des[i][0] = j;
} else if ((ab(h[i] - h[j]) < Min[i][1]) || (Min[i][1] == ab(h[i] - h[j]) && h[j] < h[des[i][0]]))
Min[i][1] = ab(h[i] - h[j]), des[i][1] = j;
}
void doubling(int i, int val) {
for (int k = 20; k >= 0; k--)
if (dis[i][k][0] + dis[i][k][1] <= val && To[i][k])
val -= (dis[i][k][0] + dis[i][k][1]), tot[1] += dis[i][k][1], tot[0] += dis[i][k][0], i = To[i][k];
if (des[i][1] && Min[i][1] <= val)
tot[1] += Min[i][1];
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &h[i]);
Min[i][1] = Min[i][0] = 2147483645;
}
for (int i = n; i >= 1; i--) {
box.insert((info) {
h[i], i
});
I = box.find((info) {
h[i], i
});
++I;
if (I != box.end())
consider(i, *I), ++I, I != box.end() ? consider(i, *I), 1 : 1, --I;
--I;
if (I != box.begin())
--I, consider(i, *I), I != box.begin() ? --I, consider(i, *I), 1 : 1;
}
for (int i = 1; i <= n; i++)
To[i][0] = des[des[i][1]][0],
dis[i][0][1] = Min[i][1], dis[i][0][0] = Min[des[i][1]][0];
for (int k = 1; k <= 20; k++)
for (int i = 1; i <= n; i++)
To[i][k] = To[To[i][k - 1]][k - 1],
dis[i][k][1] = dis[i][k - 1][1] + dis[To[i][k - 1]][k - 1][1], dis[i][k][0] = dis[i][k - 1][0] + dis[To[i][k - 1]][k - 1][0];
scanf("%d", &x);
double rate = 2147483645;
int pos = 0;
h[0] = -2147483645;
for (int i = 1; i <= n; i++) {
tot[0] = tot[1] = 0;
doubling(i, x);
double tmp = tot[0] ? 1.0 * tot[1] / tot[0] : 2147483645;
if (tmp - rate < 0.000003 && tmp - rate > -0.000003 && h[i] > h[pos])
pos = i;
if (rate - tmp > 0.000003)
pos = i, rate = tmp;
}
printf("%d\n", pos);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &s, &x);
tot[0] = tot[1] = 0;
doubling(s, x);
printf("%d %d\n", tot[1], tot[0]);
}
return 0;
}
#B. Count The Repetitions*
尝试匹配总字符。假设dp数组中dp[i]表示从s2的i字符开始匹配s1,可以匹配多少个字符。然后用dp数组做转移,统计从第1个s1开始,一直到第n1个s1,可以一共匹配s2中的多少个字符,再除以s2长度和n2即可。
#include <bits/stdc++.h>
using namespace std;
string S, T;
int N, M;
int getMaxRepetitions(string s1, int n1, string s2, int n2)
{
int len = s2.length(), dp[len];
memset(dp, 0, sizeof(dp));
for (int i = 0; i < len; i++)
{
int p = i;
for (int j = 0; j < s1.length(); j++)
if (s1[j] == s2[p])
{
p = (p + 1) % len;
dp[i]++;
}
}
int sum = 0, idx = 0;
for (int i = 0; i < n1; i++)
{
sum += dp[idx];
idx = (idx + dp[idx]) % len;
}
return sum / len / n2;
}
int main()
{
while(cin >> T >> M >> S >> N)
cout << getMaxRepetitions(S, N, T, M) << endl;
return 0;
}
#C. New Year Domino
题目大意
\(n\) 个多米诺骨牌,每个骨牌有两个值 \(x_i,h_i\) 表示每个骨牌的坐标和高度,第 \(i\) 个骨牌被推倒后,所有满足 \(x_j \leq x_i+h_i\) 的骨牌 \(j\) 也会被推倒,多次询问,问如果推倒第 \(x\) 个骨牌,最少需要花费多少代价才能推倒第 \(y\) 个骨牌。
代价定义:可以将任意一个骨牌的高度增加 \(1\),花费 \(1\) 的代价。
题意转换
每次查询下标在 \([l,r]\) 的线段所覆盖的区间中的 \(0\) 的个数。我们可以发现,对于每次查询推倒 \(l\) 骨牌,那么 \([1,l-1]\) 这些骨牌,就不应该对当前查询有所影响。
所以我们可以考虑将每次查询按左端点从大到小排序,并记一个时间戳,每次判断一下时间戳与查询区间的左端点即可。
做法
我们可以用线段树来进行区间覆盖 \(1\),区间查询 \(0\) 的数量,但是由于值域 \([1,10^9]\),普通线段树难以支持,所以我们用动态开点值域线段树(貌似离散化也可以,但是动态开点真的是暴力美学)。
我们每次插入一个新线段,那么则将区间 \([x_i+1,x_i+h_i]\) 这个区间赋为 \(1\)。注意要 \(+1\),因为每个骨牌自己的坐标不能直接赋为 \(1\),必须要由上一个骨牌更新过来才行。
一些必须要加的优化:
查询时,如果当前节点的 \(tag\)(区间赋值标记)为 \(1\),那么说明其子区间所有的值都被赋为 \(1\),所以 \(0\) 的数量为 \(0\),直接返回 \(0\) 即可。
对于每个骨牌,要将其坐标全部减去最小的坐标,防止 \(\color{blue}\text{MLE}\)。
其它的一些细节可以看下代码,写的时候注意一下就好。
#include <bits/stdc++.h>
using namespace std;
struct Domino
{
int lc, rc, sum, tag;
}Tree[8000080];
int tot = 0, n, m, rt, maxn = -1, minn = 1e9, ans[200020];;
inline void pushup(int u)
{
Tree[u].sum = Tree[Tree[u].lc].sum + Tree[Tree[u].rc].sum;
}
inline void Lazy(int u, int l, int r)
{
Tree[u].tag = 1;
Tree[u].sum = r - l + 1;
}
inline void pushdown(int u, int l, int r)
{
int mid = (l + r) >> 1;
if (Tree[u].lc == 0)
Tree[u].lc = ++tot;
if (Tree[u].rc == 0)
Tree[u].rc = ++tot;
Lazy(Tree[u].lc, l, mid);
Lazy(Tree[u].rc, mid + 1, r);
Tree[u].tag = 0;
}
inline void Modify(int &u, int l, int r, int L, int R)
{
if (L > R)
return;
if (!u)
u = ++tot;
if (l == L && r == R)
{
Lazy(u, l, r);
return;
}
if (Tree[u].tag)
pushdown(u, l, r);
int mid = (l + r) >> 1;
if (R <= mid)
Modify(Tree[u].lc, l, mid, L, R);
else if (L > mid)
Modify(Tree[u].rc, mid + 1, r, L, R);
else
{
Modify(Tree[u].lc, l, mid, L, mid);
Modify(Tree[u].rc, mid + 1, r, mid + 1, R);
}
pushup(u);
}
inline int query(int u, int l, int r, int L, int R)
{
if (!u)//查询的优化1
return r - l + 1;
if (l == L && r == R)
return r - l + 1 - Tree[u].sum;
if (Tree[u].tag)//查询的优化2
return 0;
int mid = (l + r) >> 1;
if (R <= mid)
return query(Tree[u].lc, l, mid, L, R);
else if (L > mid)
return query(Tree[u].rc, mid + 1, r, L, R);
else return query(Tree[u].lc, l, mid, L, mid) + query(Tree[u].rc, mid + 1, r, mid + 1, R);
}
struct pile
{
int x, h, id;
bool operator < (const pile &u) const { //骨牌和查询的内容和排序方式差不多
return x < u.x; //所以查询按-x加入到数组中方便排序
}
} line[200020], in[200020];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d %d", &line[i].x, &line[i].h);
minn = min(minn, line[i].x);
}
for (int i = 1; i <= n; i++) //重点优化
line[i].x = line[i].x - minn + 1;
sort(line + 1, line + 1 + n);
scanf("%d", &m);
for (int i = 1, u; i <= m; i++)
{
scanf("%d %d", &u, &in[i].h);
in[i].x = -u;
in[i].id = i;
maxn = max(maxn, -in[i].x);
}
for (int i = n; i >= maxn; i--)
Modify(rt, 1, 1e9, line[i].x + 1, min(line[i].x + line[i].h, int(1e9)));
sort(in + 1, in + 1 + m);
for (int i = 1; i <= m; i++)
{
while (maxn > -in[i].x)
{
maxn--;
Modify(rt, 1, 1e9, line[maxn].x + 1, min(line[maxn].x + line[maxn].h, int(1e9)));
}
ans[in[i].id] = query(rt, 1, 1e9, line[-in[i].x].x, line[in[i].h].x) - query(rt, 1, 1e9, line[-in[i].x].x, line[-in[i].x].x);//减去后面那个是为了防止,推倒了骨牌l,但是骨牌l的坐标值为0导致多算了一个0
}
for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
return 0;
}
#D. Sum Over Zero
前置
\(dp_i\) 表示到位置 \(i\) 为止有多少点没有选。
\(sum_i\) 表示 \(\sum_{i=1}^{n}a_i\)。
关于为什么要令 \(dp_i\) 是没有选的最优解:\(dp\) 表示选与不选在复杂度 \(O(n^2)\) 时是都可以实现的,但是在复杂度 \(O(n\log{n})\) 时,由于要将之前的信息放到数据结构里,这个信息是不能随位置而改变的,但是如果是 \(dp_i\) 表示选的话,dp 转移方程就是 \(dp_i=\max(dp_j+i-j)\),这个信息会改变所以不适合用数据结构维护。这也是“正难则反”的思想。
朴素 dp
\(dp_i\) 可以由两种方法转移:
- \(dp_i\) 不取,此时 \(dp_i=dp_{i-1}+1\)。
- \(dp_i\) 取,从 \(1\) 到 \(i-1\) 枚举 \(j\)。考虑什么样的 \(j\) 可以转移,即 \(j+1\) 到 \(i\) 这一段都取了,即 \(sum_i-sum_j\geq0\)。此时 \(dp_i=\min(dp_j)\)。
复杂度 \(O(n^2)\)。
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1] + 1;
for (int j = 1; j < i; j++)
if (sum[i] >= sum[j])
dp[i] = min(dp[i], dp[j]);
}
如何优化
这个 dp 的复杂度不够优秀,我们考虑用数据结构维护。
发现对于 \(i\) 查询的是,位置在 \(i\) 之前,前缀和小于 \(i\) 的所有 \(dp\) 的最小值。用线段树(其他数据结构也可以)维护这个信息。
将前缀和离散化,更新时查询比当前前缀和小的最小值,在完成 \(dp_i\) 的更新之后将其插入线段树。
复杂度 \(O(n\log{n})\)。
#include <bits/stdc++.h>
using namespace std;
long long n, val[200020], dp[200020], sum[200020], Sum[200020];
struct Node
{
long long val;
}Tree[4000040];
inline void build(long long K, long long l, long long r)
{
Tree[K].val = 0x3f3f3f3f;
if (l == r)
return ;
long long mid = l + r >> 1;
build(K << 1, l, mid);
build(K << 1 | 1, mid + 1, r);
}
inline void modify(long long K, long long l, long long r, long long pos, long long val)
{
if (l == r)
{
Tree[K].val = min(Tree[K].val, val);
return ;
}
long long mid = (l + r) >> 1;
if (pos <= mid)
modify(K << 1, l, mid, pos, val);
else modify(K << 1 | 1, mid + 1, r, pos, val);
Tree[K].val = min(Tree[K << 1].val, Tree[K << 1 | 1].val);
}
inline long long query(long long K, long long l, long long r, long long L, long long R)
{
if (L <= l && R >= r)
return Tree[K].val;
long long mid = l + r >> 1, ret = 0x3f3f3f3f;
if (L <= mid)
ret = min(ret, query(K << 1, l, mid, L, R));
if (R > mid)
ret = min(ret, query(K << 1 | 1, mid + 1, r, L, R));
return ret;
}
int main()
{
scanf("%lld", &n);
for (long long i = 1; i <= n; i++)
{
scanf("%lld", &val[i]);
sum[i] = val[i] + sum[i - 1];
Sum[i] = sum[i];
}
sort(Sum, Sum + n + 1);
long long len = unique(Sum, Sum + n + 1) - Sum;
build(1, 0, len);
long long Rnk = lower_bound(Sum, Sum + len, 0) - Sum;
modify(1, 0, len, Rnk, 0);
for (long long i = 1; i <= n; i++)
{
dp[i] = dp[i - 1] + 1;
if (sum[i] >= 0)
dp[i] = 0;
long long pos = lower_bound(Sum, Sum + len, sum[i]) - Sum, tmp = query(1, 0, len, 0, pos);
dp[i] = min(dp[i], tmp);
modify(1, 0, len, pos, dp[i]);
}
printf("%lld", n - dp[n]);
return 0;
}
#E. Hot Start Up (hard version)
考虑将问题转化一下,将我们要完成的任务划分为若干段区间,然后两个处理器交错处理这些区间。显然,这和原问题等价,考虑以此为切入点 dp。
设 \(f(i,j)\) 表示先执行 \(i\) 再执行 \(j\),\(j\) 的花费,\(calc(l,r)\) 表示处理完任务 \(l\) 后,顺次执行区间 \([l+1,r]\) 内所有任务的总花费。
设 \(dp_{i}\) 表示以 \(i\) 为一段区间结尾,前面划分后的最小代价。
那么枚举上一段区间结束位置,不难得到转移式:\(dp_{i}=\min\limits_{k<i}\{dp_{k}+calc(k+1,i)+f(las,k+1)\}\)。
此时我们会发现遇到了一点问题:我们无从得知 \(f(las,k+1)\) 的值。如果要再记录一维 \(las\),状态数肯定爆炸。
这里采用一个很经典的思想:费用预支。
\(f(las,k+1)\) 无法在 \((k+1,i)\) 的区间里处理,那么我们就在 \((las+1,k)\) 的区间转移时把它求出来。
所以正确的转移式为:
特别的,我们在 \(0\) 和 \(n+1\) 处均加入一个花费恒为 \(0\) 的任务,规避掉一些特殊情况。
固定左端点,不断扩大右端点即可在 \(O(n^2)\) 内预处理所有的 \(calc(l,r)\),转移 \(O(n^2)\),可以通过 easy version。
接下来考虑优化,先处理 \(calc\) 函数。
令 \(g_i=f(i,i+1)\),则有 \(calc(l,r)=\sum\limits_{i=l}^{r-1}g_i\)。
所以我们求出 \(g\) 的前缀和数组 \(sum_i=\sum\limits_{j=0}^ig_j\),即可得到 \(calc(l,r)=sum_{r-1}-sum_{l-1}\)。
将其代回原转移式可得 \(dp_{i}=\min\limits_{k<i}\{dp_k+sum_{i-1}-sum_k+f(k,i+1)\}\)。
按下标分类一下,\(dp_i=\min\limits_{k<i}\{dp_k-sum_k+f(k,i+1)\}+sum_{i-1}\)。
前面部分的 \(dp\) 求完后,\(dp_k-sum_k\) 是可以统一维护的,那么只剩下 \(f(k,i+1)\) 一项了。考虑这东西等于什么。
由于保证 \(hot_i\le cold_i\),所以实际上还可以更进一步得到:
因此转移式也可以进行调整。
那么我们只需要在 dp 的过程中顺带维护 \(dp_i-sum_i\) 的全局最小值和每类最小值即可,复杂度 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
long long T, n, k, a[300030], hot[300030], cold[300030], sum[300030], dp[300030], mn[300030];
inline long long calc(long long x, long long y)
{
return a[x] == a[y] ? hot[a[y]] : cold[a[y]];
}
int main()
{
scanf("%lld", &T);
while (T--)
{
scanf("%lld %lld", &n, &k);
for (long long i = 1; i <= n; i++)
sum[i] = dp[i] = 0;
for (long long i = 1; i <= k; i++)
mn[i] = 1e18;
a[n + 1] = hot[n + 1] = cold[n + 1] = 0;
for (long long i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for (long long i = 1; i <= k; i++)
scanf("%lld", &cold[i]);
for (long long i = 1; i <= k; i++)
scanf("%lld", &hot[i]);
sum[0] = calc(0, 1);
for (long long i = 1; i <= n; i++)
sum[i] = sum[i - 1] + calc(i, i + 1);
long long alm = 0;
for (long long i = 1; i <= n; i++)
{
dp[i] = alm + cold[a[i + 1]] + sum[i - 1];
dp[i] = min(dp[i], mn[a[i + 1]] + hot[a[i + 1]] + sum[i - 1]);
mn[a[i]] = min(mn[a[i]], dp[i] - sum[i]);
alm = min(alm, dp[i] - sum[i]);
}
printf("%lld\n", dp[n]);
}
return 0;
}
#F. Non-equal Neighbours
设 \(f(i,j)\) 表示考虑前 \(i\) 个数,当前 \(b_i=j\) 的方案数,那么显然有:
把 \(f(i)\) 看作序列,用线段树维护即可。需要支持区间推平(\(j>a_i\) 的情况),区间取反(\(f(i,j)\gets -f(i-1,j)\)),区间加(\(f(i,j)\gets -f(i-1,j)+\sum_{x=1}^{a_{i-1}}f(i-1,x)\)),区间查询(查询 \(\sum_{x=1}^{a_{i-1}}f(i-1,x)\))。
#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
long long ans, c, n, a[200020], b[200020];
inline void Mod(long long &x)
{
x = (x % mod + mod) % mod;
}
struct Segment_Tree
{
struct seg
{
long long l, r, sum, cov, add, neg;
seg() {l = r = sum = add = cov = neg = 0;}
} Tree[800080];
inline void build(long long K, long long l, long long r)
{
Tree[K].l = l;
Tree[K].r = r;
if (l == r)
return;
long long mid = (l + r) >> 1;
build(K << 1, l, mid);
build(K << 1 | 1, mid + 1, r);
}
inline void pushdown(long long K)
{
if (Tree[K].cov)
{
Tree[K << 1].cov = Tree[K << 1 | 1].cov = Tree[K].cov;
Tree[K].cov = 0;
Tree[K << 1].neg = Tree[K << 1 | 1].neg = 0;
Tree[K << 1].add = Tree[K << 1 | 1].add = 0;
Tree[K << 1].sum = 0;
Tree[K << 1 | 1].sum = 0;
}
if (Tree[K].neg)
{
Tree[K << 1].neg ^= 1;
Tree[K << 1 | 1].neg ^= 1;
Tree[K << 1].add *= -1;
Tree[K << 1 | 1].add *= -1;
Mod(Tree[K << 1].add);
Mod(Tree[K << 1 | 1].add);
Tree[K << 1].sum *= -1;
Tree[K << 1 | 1].sum *= -1;
Mod(Tree[K << 1].sum);
Mod(Tree[K << 1 | 1].sum);
Tree[K].neg = 0;
}
if (Tree[K].add)
{
Tree[K << 1].add += Tree[K].add, Tree[K << 1].add %= mod;
Tree[K << 1].sum += 1ll * (b[Tree[K << 1].r] - b[Tree[K << 1].l - 1]) * Tree[K].add % mod, Tree[K << 1].sum %= mod;
Tree[K << 1 | 1].add += Tree[K].add, Tree[K << 1 | 1].add %= mod;
Tree[K << 1 | 1].sum += 1ll * (b[Tree[K << 1 | 1].r] - b[Tree[K << 1 | 1].l - 1]) * Tree[K].add % mod, Tree[K << 1 | 1].sum %= mod;
Tree[K].add = 0;
}
}
inline void modify(long long K, long long l, long long r, long long d)
{ //add
if (l > r)
return;
if (l <= Tree[K].l && Tree[K].r <= r)
{
Tree[K].sum += 1ll * (b[Tree[K].r] - b[Tree[K].l - 1]) * d % mod;
Tree[K].sum %= mod;
Tree[K].add += d;
Tree[K].add %= mod;
return;
}
pushdown(K);
long long mid = (Tree[K].l + Tree[K].r) >> 1;
if (l <= mid)
modify(K << 1, l, r, d);
if (r > mid)
modify(K << 1 | 1, l, r, d);
Tree[K].sum = Tree[K << 1].sum + Tree[K << 1 | 1].sum;
Tree[K].sum %= mod;
}
inline void cover(long long K, long long l, long long r)
{
if (l > r)
return;
if (l <= Tree[K].l && Tree[K].r <= r)
{
Tree[K].cov = 1;
Tree[K].add = Tree[K].sum = Tree[K].neg = 0;
return;
}
pushdown(K);
long long mid = (Tree[K].l + Tree[K].r) >> 1;
if (l <= mid)
cover(K << 1, l, r);
if (r > mid)
cover(K << 1 | 1, l, r);
Tree[K].sum = Tree[K << 1].sum + Tree[K << 1 | 1].sum;
Tree[K].sum %= mod;
}
inline void Modify(long long K, long long l, long long r)
{
if (l > r)
return;
if (l <= Tree[K].l && Tree[K].r <= r)
{
Tree[K].neg ^= 1;
Tree[K].sum *= -1, Tree[K].add *= -1;
Mod(Tree[K].sum);
Mod(Tree[K].add);
return;
}
pushdown(K);
long long mid = (Tree[K].l + Tree[K].r) >> 1;
if (l <= mid)
Modify(K << 1, l, r);
if (r > mid)
Modify(K << 1 | 1, l, r);
Tree[K].sum = Tree[K << 1].sum + Tree[K << 1 | 1].sum;
Tree[K].sum %= mod;
}
inline long long query(long long K, long long l, long long r)
{
if (l > r)
return 0;
if (l <= Tree[K].l && Tree[K].r <= r)
return Tree[K].sum;
pushdown(K);
long long mid = (Tree[K].l + Tree[K].r) >> 1, ans = 0;
if (l <= mid)
ans = (ans + query(K << 1, l, r)) % mod;
if (r > mid)
ans = (ans + query(K << 1 | 1, l, r)) % mod;
return ans;
}
} T;
int main()
{
scanf("%lld", &n);
c = n;
// 离散化一波
for (long long i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
b[i] = a[i];
}
sort(b + 1, b + c + 1);
c = unique(b + 1, b + c + 1) - b - 1;
for (long long i = 1; i <= n; i++)
a[i] = lower_bound(b + 1, b + c + 1, a[i]) - b;
// for(long long i = 1; i <= n; i++) printf("%d ", a[i]);puts("!!!");
T.build(1, 1, c);
for (long long i = 1; i <= a[1]; i++)
T.modify(1, i, i, 1);
for (long long i = 2; i <= n; i++)
{
long long sum = T.query(1, 1, c);
// printf("sum = %lld\n", sum);
T.cover(1, a[i] + 1, c);
// printf("sum = %lld\n", T.query(1, 1, c));
T.Modify(1, 1, a[i]);
// printf("sum = %lld\n", T.query(1, 1, c));
T.modify(1, 1, a[i], sum);
// printf("sum = %lld\n", T.query(1, 1, c));
}
for (long long i = 1; i <= a[n]; i++)
ans = (ans + T.query(1, i, i)) % mod;
printf("%lld", ans);
return 0;
}
#G. Animal Observation (hard version)
Discription.
给定一个矩阵,现在你可以对每一行取出一个这一行和下一行组成的 \(2\times k\) 的子矩阵,并取走其中的所有数(如果最后一行的话就只能取一行。
现在你需要让你取出的所有数最大化。
Solution.
首先,有一个很显然的 \(O(nm^2)\) 的 dp。
设 \(dp_{i,j}\) 表示第 \(i\) 和 \(i+1\) 行选 \([j,j+k-1]\) 这个子矩阵时的最大答案。
那么 \(dp_{i,j}\) 可以从所有的 \(dp_{i-1,k}\) 转移过来,主要就是去重。
但是如果暴力枚举时肯定能算出去重函数的,所以我们得到了一份 TLE on \(13\) 的代码,如下
#include <bits/stdc++.h>
using namespace std;
long long ans, n, m, K, a[55][20020], pre[55][20020], dp[55][20020];
inline long long calc(long long i, long long l, long long r)
{
if (l > r)
swap(l, r);
return max(pre[i][l + K - 1] - pre[i][r - 1], 0ll);
}
int main()
{
scanf("%lld %lld %lld", &n, &m, &K);
for (long long i = 1; i <= n; i++)
for (long long j = 1; j <= m; j++)
{
scanf("%lld", &a[i][j]);
pre[i][j] = pre[i][j - 1] + a[i][j];
}
for (long long j = 1; j <= m - K + 1; j++)
dp[1][j] = pre[1][j + K - 1] - pre[1][j - 1] + pre[2][j + K - 1] - pre[2][j - 1];
for (long long i = 2; i <= n; i++)
for (long long j = 1; j <= m - K + 1; j++)
for (long long k = 1; k <= m - K + 1; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + pre[i + 1][j + K - 1] - pre[i + 1][j - 1] + pre[i][j + K - 1] - pre[i][j - 1] - calc(i, k, j));
for (long long j = 1; j <= m - K + 1; j++)
ans = max(ans, dp[n][j]);
printf("%lld", ans);
return 0;
}
然后我们就能获得……\(0\) 分的好成绩,毕竟 F1 都不让这种方法过。
然后我们考虑优化,F1 那题有一个限制 \(k\le\min(20,m)\)。
思考思考这个限制有什么用:我们会发现,因为上面的重复(calc)中有一个 max,我们考虑去掉它。
首先,矩阵中所有的数都是正数,那么也就是说只有在 l+K-1>=r 时才会取到 pre[i][l + K - 1] - pre[i][r - 1]。
那么这就好办了,只有在 \(j-K+1\le k\le j+K-1\) 时那个 calc 函数才会有用。
然后又因为 \(k\) 很小,那么我们直接暴力转移这样的状态,剩下的因为 calc 函数等于 \(0\),所以我们直接记录前缀最大值后缀最大值就好了,这样复杂度是 \(O(nmk)\) 的,足以通过 F1,代码
#include <bits/stdc++.h>
using namespace std;
long long ans, n, m, K, a[55][20020], s[55][20020], dp[55][20020], mx1[55][25005], mx2[55][25005];
inline long long calc(long long i, long long l, long long r)
{
if (l > r)
swap(l, r);
return l + K - 1 >= r ? s[i][l + K - 1] - s[i][r - 1] : 0;
}
int main()
{
scanf("%lld %lld %lld", &n, &m, &K);
for (long long i = 1; i <= n; i++)
for (long long j = 1; j <= m; j++)
{
scanf("%lld", &a[i][j]);
s[i][j] = s[i][j - 1] + a[i][j];
}
for (long long j = 1; j <= m - K + 1; j++)
dp[1][j] = s[1][j + K - 1] - s[1][j - 1] + s[2][j + K - 1] - s[2][j - 1];
for (long long j = 1; j <= m - K + 1; j++)
mx1[1][j] = max(mx1[1][j - 1], dp[1][j]);
for (long long j = m - K + 1; j >= 1; j--)
mx2[1][j] = max(mx2[1][j + 1], dp[1][j]);
for (long long i = 2; i <= n; i++)
{
for (long long j = 1; j <= m - K + 1; j++)
{
long long now = s[i + 1][j + K - 1] - s[i + 1][j - 1] + s[i][j + K - 1] - s[i][j - 1];
dp[i][j] = max(dp[i][j], now + max(j > K ? mx1[i - 1][j - K] : 0, mx2[i - 1][j + K]));
for (long long k = max(j - K + 1, 1ll); k <= min(j + K - 1, m - K + 1); k++)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + now - calc(i, k, j));
}
for (long long j = 1; j <= m - K + 1; j++)
mx1[i][j] = max(mx1[i][j - 1], dp[i][j]);
for (long long j = m - K + 1; j >= 1; j--)
mx2[i][j] = max(mx2[i][j + 1], dp[i][j]);
}
for (long long j = 1; j <= m - K + 1; j++)
ans = max(ans, dp[n][j]);
printf("%lld", ans);
return 0;
}
然后,我们继续思考,如果 \(k\) 很大这样复杂度还是无法通过 F2,我们仔细观察一下,发现上面的暴力转移过程中还可以继续分类讨论,如下
for (int k = max(j - K + 1, 1); k <= j; k++)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + now - pre[i][k + K - 1] + pre[i][j - 1]);
for (int k = j; k <= min(j + K - 1, m - K + 1); k++)
dp[i][j] = max(dp[i][j], dp[i - 1][k] + now - pre[i][j + K - 1] + pre[i][k - 1]);
那么这样就很显然了吧。
在上面那一行中,now + pre[i][j-1]是不变的,而 dp[i - 1][k] + pre[i][k + K - 1]与 \(j\) 无关。
下面那一行中,now - pre[i]是不变的,而dp[i - 1][k] + pre[i][k - 1]是与 \(j\) 无关的。
那么我们只需要用线段树等数据结构来维护区间最大值就好了,复杂度是 \(O(nm\log m)\) 。
#include <bits/stdc++.h>
using namespace std;
long long ans, n, m, K, a[55][20020], pre[55][20020], dp[55][20020], mx1[55][25005], mx2[55][25005], t[25005];
inline long long calc(long long i, long long l, long long r)
{
if (l > r)
swap(l, r);
return pre[i][l + K - 1] - pre[i][r - 1];
}
struct Segment_Tree
{
long long t[100010];
inline void build(long long x, long long l, long long r, long long *c)
{
if (!(l ^ r))
{
t[x] = c[l];
return;
}
build(x << 1, l, (l + r) >> 1, c);
build(x << 1 | 1, ((l + r) >> 1) + 1, r, c);
t[x] = max(t[x << 1], t[x << 1 | 1]);
}
inline long long query(long long x, long long l, long long r, long long L, long long R)
{
if (L > r || l > R)
return -1e9;
else if (L <= l && r <= R)
return t[x];
return max(query(x << 1, l, (l + r) >> 1, L, R), query(x << 1 | 1, ((l + r) >> 1) + 1, r, L, R));
}
}T1, T2;
int main()
{
scanf("%lld %lld %lld", &n, &m, &K);
for (long long i = 1; i <= n; i++)
for (long long j = 1; j <= m; j++)
{
scanf("%lld", &a[i][j]);
pre[i][j] = pre[i][j - 1] + a[i][j];
}
for (long long j = 1; j <= m - K + 1; j++)
dp[1][j] = pre[1][j + K - 1] - pre[1][j - 1] + pre[2][j + K - 1] - pre[2][j - 1];
for (long long j = 1; j <= m - K + 1; j++)
mx1[1][j] = max(mx1[1][j - 1], dp[1][j]);
for (long long j = m - K + 1; j >= 1; j--)
mx2[1][j] = max(mx2[1][j + 1], dp[1][j]);
for (long long i = 2; i <= n; i++)
{
for (long long k = 1; k <= m - K + 1; k++)
t[k] = dp[i - 1][k] - pre[i][k + K - 1];
T1.build(1, 1, m - K + 1, t);
for (long long k = 1; k <= m - K + 1; k++)
t[k] = dp[i - 1][k] + pre[i][k - 1];
T2.build(1, 1, m - K + 1, t);
for (long long j = 1; j <= m - K + 1; j++)
{
long long now = pre[i + 1][j + K - 1] - pre[i + 1][j - 1] + pre[i][j + K - 1] - pre[i][j - 1];
dp[i][j] = max(dp[i][j], now + max(j > K ? mx1[i - 1][j - K] : 0, mx2[i - 1][j + K]));
dp[i][j] = max(dp[i][j], T1.query(1, 1, m - K + 1, max(j - K + 1, 1ll), j) + now + pre[i][j - 1]);
dp[i][j] = max(dp[i][j], T2.query(1, 1, m - K + 1, j, min(j + K - 1, m - K + 1)) + now - pre[i][j + K - 1]);
// for(long long k = max(j - K + 1, 1); k <= j; k++) dp[i][j] = max(dp[i][j],dp[i - 1][k] + now - pre[i][k + K - 1] + pre[i][j - 1]);
// for(long long k = j; k <= min(j + K - 1, m - K + 1); k++) dp[i][j] = max(dp[i][j], dp[i - 1][k] + now - pre[i][j + K - 1] + pre[i][k - 1]);
}
for (long long j = 1; j <= m - K + 1; j++)
mx1[i][j] = max(mx1[i][j - 1], dp[i][j]);
for (long long j = m - K + 1; j >= 1; j--)
mx2[i][j] = max(mx2[i][j + 1], dp[i][j]);
}
for (long long i = 1; i <= m - K + 1; i++)
ans = max(ans, dp[n][i]);
printf("%lld", ans);
return 0;
}
#H. Artistic Partition
题意
记 \(c(l,r)=\sum\limits_{i=l}^r\sum\limits_{j=i}^r[\gcd(i,j)\ge l]\)。你可以将\(1\sim n\) 的\(n\) 个数分成 \(k\) 段 \(0=x_1<x_2<x_3<\cdots<x_k<x_{k+1}=n\),最小化
数据范围:\(n,k\le 10^5,T\) 组询问, \(T\le 3\times 10^5\)。
solution
一个暴力的 DP 做法是:设 \(f_{i,k}\) 表示前 \(i\) 个数分成 \(k\) 段的最小值。转移是
边界:\(f_{i,0}=+\inf\),\(f_{0,k}=0\)。
询问数很多,可以预处理出 DP 数组 \(O(1)\) 查询。
DP状态数是 \(O(n^2)\) 的,无法承受。观察 \(c(l,r)\) 的性质,发现 \(c(l,r)\ge r-l+1\),也就是说 \(f_{n,k}\ge n\)。其次,\(c(l,2l-1)=(2l-1)-l+1=l\),也就是说,若 \(n<2^k\),一定可以把 \(n\) 分成若干段,满足 \(c(l,r)=r-l+1\),即\(f_{n,k}=n(2^k>n)\)。剩下的需要考虑的 \(k\) 就是 \(O(\log n)\) 级别的了。
状态数没有太大的优化空间了,考虑优化 \(c(l,r)\) 的计算。大力推式子:
记 \(s(n)=\sum_{i=1}^n\varphi(i)\),即 \(\varphi\) 的前缀和。则
预处理出 \(s(n)\) 之后即可 \(O(\sqrt{r})\) 查询。实际上,\(c(l,r)\) 的计算复杂度大概是 \(O(\sqrt{r-l})\) 的。或者可以 \(O(n\sqrt{n})\) 预处理 \(O(1)\) 查询。
考虑优化 DP 转移。根据这个 DP 分段的形式,大胆猜测 \(c(l,r)\) 满足四边形不等式,那么这样DP转移就可以使用决策单调性优化转移了。\(c(l,r)\) 满足四边形不等式的证明如下:
设 \(l_1<l_2<r_1<r_2\),我们需要证明 \(c(l_1,r_1)+c(l_2,r_2)\le c(l_1,r_2)+c(l_2,r_1)\)。
设 \(f(l,r,p)=\sum_{k=l}^p(\lfloor\frac{r}{k}\rfloor)\),那么
显然 \(f(l_1,r_2,l_2-1)\ge f(l_1,r_1,l_2-1)\),得证。
剩下的部分可以使用 \(\text{1D1D}\) 决策单调性优化 \(\text{DP}\) 的常见套路(分治/单调队列)进行。因为有 \(O(\log n)\) 层,每层的复杂度是 \(O(n\log n)\) 的,转移的时间复杂度是 \(O(n\log^2n)\) 的,加上预处理的复杂度,总时间复杂度 \(O(n\log^2n+n\sqrt{n})\) ,空间复杂度 \(O(n\sqrt{n})\)。
实际上,采用分治做法,假设当前的转移区间是 \([L,R]\),当前需要寻找转移点的是 \(mid\)。首先\(O(\sqrt{r-l})\) 求出 \(c(R+1,mid)\),然后从 \(\min(mid,R)\) 到 \(L\) 枚举转移点 \(i\),\(c(i,mid)=c(i+1,mid)+s(\lfloor\frac{mid}{i}\rfloor)\)。这个做法的时间复杂度不太好证,不过预处理跑的很快,而且代码复杂度很低。
#include <bits/stdc++.h>
#define inf 1e18
using namespace std;
long long prime[100010], pcnt, phi[100010], T, n, k, dp[100010][18];
bool vis[100010];
inline void init(long long n)
{
phi[1] = 1;
for (long long i = 2; i <= n; ++i)
{
if (!vis[i])
{
prime[++pcnt] = i;
phi[i] = i - 1;
}
for (long long j = 1; j <= pcnt && prime[j] * i <= n; ++j)
{
vis[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
for (long long i = 1; i <= n; ++i)
phi[i] += phi[i - 1];
}
inline long long calc(long long l, long long r)
{
long long res = 0;
for (long long i; l <= r; l = i + 1)
{
i = min(r / (r / l), r);
res += phi[r / l] * (i - l + 1);
}
return res;
}
inline void query(long long l, long long r, long long L, long long R)
{
if (l > r)
return;
long long mid = (l + r) >> 1, sum = calc(R + 1, mid), pos = 0, mn = inf;
for (long long i = min(mid, R); i >= L; --i)
{
sum += phi[mid / i];
if (sum + dp[i - 1][k - 1] <= mn)
{
mn = sum + dp[i - 1][k - 1];
pos = i;
}
}
dp[mid][k] = mn;
query(l, mid - 1, L, pos);
query(mid + 1, r, pos, R);
}
int main()
{
init(100000);
for (long long i = 1; i <= 100000; ++i)
dp[i][1] = (i * (i + 1)) >> 1;
for (k = 2; k <= 17; ++k)
query(1, 100000, 1, 100000);
scanf("%lld", &T);
while (T--)
{
scanf("%lld %lld", &n, &k);
if (k >= 20 || n < (1 << k))
{
printf("%lld\n", n);
continue;
}
printf("%lld\n", dp[n][k]);
}
return 0;
}
#I. Another n-dimensional chocolate bar
看到积式,我们自然能想到用 dp 来解决此类问题。
设 \(f_{i,j}\) 表示前 \(b_{1,2,\cdots,i}\) 的值已经确定,后面的数的限制为 \(\prod\limits_{p=i+1}^n b_p\geq j\) 的前提下, \(\prod\limits_{p=1}^i\lfloor\dfrac{a_p}{b_p}\rfloor \times \dfrac{1}{a_p}\) 的最大值。
自然可以通过枚举 \(b_i\) 的值来转移:
令 \(b_i=x\) ,则有转移: \(f_{i,\lceil\frac{j}{x}\rceil}\leftarrow f_{i-1,j}\times\lfloor\dfrac{a_p}{x}\rfloor \times \dfrac{1}{a_p}\)
初始状态为: \(f_{0,k}=1\),答案为: \(f_{n,1}\times k\)。
直接转移是 \(O(nk)\) 的,考虑优化。
可以发现,第二维的值是由 \(k\) 不断除以整数后向上取整得到的。
向上取整有一个性质:
\(\lceil\dfrac{k}{a}\rceil=\lfloor\dfrac{k-1}{a}\rfloor+1\)
而且这个式子可以嵌套:
\(\lceil\dfrac{\lceil\frac{k}{a}\rceil}{b}\rceil=\lfloor\dfrac{\lfloor\frac{k-1}{a}\rfloor+1-1}{b}\rfloor+1=\lfloor\dfrac{\lfloor\frac{k-1}{a}\rfloor}{b}\rfloor+1=\lfloor\dfrac{k-1}{ab}\rfloor+1\)
这样我们就可以把向上取整转化为向下取整了,用类似杜教筛的转移方式就可以得到 \(O(nk^{0.75})\) 的做法。
#include <bits/stdc++.h>
using namespace std;
long long n, k, a[110], v[7070], m, id[10000010];
bool vis[110][7070];
double f[110][7070];
int main()
{
scanf("%lld %lld", &n, &k);
for (long long i = 1; i <= n; ++i)
scanf("%lld", &a[i]);
if (k == 1)
{
double ans = 1.0;
printf("%.20lf\n", ans);
return 0;
}
for (long long l = 1, r; l < k; l = r + 1)
{
r = (k - 1) / ((k - 1) / l);
v[++m] = (k - 1) / l;
id[v[m]] = m;
}
v[++m] = 0;
id[0] = m;
f[0][1] = 1;
for (long long i = 1; i <= n; ++i)
for (long long j = 1; j <= m; ++j)
if (f[i - 1][j])
{
for (long long l = 1, r; l <= v[j]; l = r + 1)
{
r = v[j] / (v[j] / l);
f[i][id[v[j] / r]] = max(f[i][id[v[j] / r]], (a[i] / l) / (double)a[i] * f[i - 1][j]);
}
f[i][m] = max(f[i][m], (a[i] / (v[j] + 1)) / (double)a[i] * f[i - 1][j]);
}
printf("%.20lf", f[n][m] * k);
return 0;
}
#J. [JRKSJ R4] kth
题意
给定一个 \([1,n]\) 的排列 \(p\) ,以任意点为起点出发,每次可以向左或向右移动一步,求经过 \(m\) 个点(即相当于走 \(m-1\) 步)后可以产生的序列中字典序第 \(k\) 小的序列。
分析
对于 \(20\%\) 的数据,有 \(m,n\leq70\) 。我们很显然可以利用 dp 来求解,设 \(dp_{i,j}\) 表示从第 \(i\) 个位置开始,走 \(j-1\) 步,经过 \(j\) 个点的方案数,则有转移方程:
\(dp_{i,j}=dp_{i-1,j-1}+dp_{i+1,j-1}\)
考虑第一步可以走到第 \(i-1\) 个位置或者第 \(i+1\) 个位置,很容易知道,这个转移方程是显然的。
我们预处理出所有方案数,利用 dfs 找出每一步应走的位置,具体方法为:
设当前位置为 \(i\) ,还需要经过 \(x\) 个点(还需走 \(x\) 步),查找第 \(k\) 小的排列。若 \(p_{i-1}<p_{i+1}\) ,令 \(j=i-1\) ,否则令 \(j=i+1\) ,此时若 \(dp_{j,x}\geq k\) ,则直接进入 \(j\) 继续搜索,反之,则令 \(k\) 减去 \(dp_{j,x}\) ,进入另一侧进行搜索。
代码
dp 部分
for(int i=1;i<=n;i++) dp[i][1]=1;
for(int j=2;j<=m;j++){
for(int i=1;i<=n;i++){
dp[i][j]=dp[i-1][j-1]+dp[i+1][j-1];
}
}
dfs 部分
void dfs(int pos,int last,int num){
if(!pos) return;
int now=0,tmp=n;
if(last==1){
ans+=p[2]; ans%=mod;
return dfs(pos-1,2,num);
}
if(last==n){
ans+=p[n-1]; ans%=mod;
return dfs(pos-1,n-1,num);
}
if(p[last-1]>p[last+1]){
if(num>dp[last+1][pos]){
ans+=p[last-1]; ans%=mod;
dfs(pos-1,last-1,num-dp[last+1][pos]);
}
else{
ans+=p[last+1]; ans%=mod;
dfs(pos-1,last+1,num);
}
}
else{
if(num>dp[last-1][pos]){
ans+=p[last+1]; ans%=mod;
dfs(pos-1,last+1,num-dp[last-1][pos]);
}
else{
ans+=p[last-1]; ans%=mod;
dfs(pos-1,last-1,num);
}
}
}
优化
我们发现,这个转移的时间复杂度是 \(O(nm)\) , dfs 的时间复杂度是 \(O(m)\) ,显然无法通过本题数据范围的数据,并且, dp 的转移过程中,数据呈现指数级增长,即使采用高精度也不能在合理的空间复杂度内存储。然而, \(k\) 很小,在 \(10^{18}\) 范围内。而我们可以推断出,比 \(k\) 小的 dp 值,一定在 \(\log_2k\) 的量级左右。因此,前面的大多数步一定在取字典序最小的部分,也就是说,一直在 \(1\) 与 \(1\) 左右两侧中较小的数中循环。所以,这样我们将 \(m\) 转化为 \(\log_2k\) 的量级,时间复杂度和空间复杂度均为 \(O(n\log k)\) ,可以拿到 \(70\) 分的成绩。
再进一步,发现在 \(i\geq j\) 时,每一步均有两种走法,即有 \(dp_{i,j}=2^{j-1}\) ,因此,考虑只转移 \(i<j\) 的部分,当 \({i\geq j}\) 时,直接返回 \(2^{j-1}\) 即可。利用记忆化搜索可以更方便实现。
最终,我们成功地把转移过程中对于 \(n,m\) 的计算均降到了 \(\log_2k\) 的量级内,可以通过本题。
时间复杂度为 \(O(n)\) (读入数据的时间复杂度)。
注意要用 __int128!!!
//码量是真的大woc
//错一次都得对着170行的代码硬调。。。。。
//差点调死我。。。。。。
#include <bits/stdc++.h>
using namespace std;
const __int128 inf = 2e18;
const __int128 mod = 1ll << 32;
signed p[20000005] = {}, unp[20000005] = {};
__int128 ans = 0, memory[1005][1005] = {}, Base[100] = {};
long long n, m, k;
inline long long read()
{
long long x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void write(__int128 x)
{
if(x > 9)
write(x / 10);
putchar(x % 10 + 48);
}
inline __int128 ksm(__int128 x, __int128 y)
{
__int128 res = 1;
while(y)
{
if(y & 1)
res *= x;
x *= x;
y >>= 1;
}
return res;
}
inline __int128 f(__int128 i, __int128 j)
{
if(i <= 0 || i > n)
return 0;
i = min(i, n + 1 - i);
if(i >= j && j > 70)
return inf;
if(i >= j)
return Base[j - 1];
if(memory[i][j])
return memory[i][j];
return memory[i][j] = min(inf, f(i - 1, j - 1) + f(i + 1, j - 1));
}
inline void dfs(__int128 pos, __int128 last, __int128 num)
{
if(!pos)
return;
__int128 now = 0, tmp = n;
if(last == 1)
{
ans += p[2];
ans %= mod;
return dfs(pos - 1, 2, num);
}
if(last == n)
{
ans += p[n - 1];
ans %= mod;
return dfs(pos - 1, n - 1, num);
}
if(p[last - 1] > p[last + 1])
if(num > f(last + 1, pos))
{
ans += p[last - 1];
ans %= mod;
dfs(pos - 1, last - 1, num - f(last + 1, pos));
}
else
{
ans += p[last + 1];
ans %= mod;
dfs(pos - 1, last + 1, num);
}
else
if(num > f(last - 1, pos))
{
ans += p[last + 1];
ans %= mod;
dfs(pos - 1, last + 1, num - f(last - 1, pos));
}
else
{
ans += p[last - 1];
ans %= mod;
dfs(pos - 1, last - 1, num);
}
}
inline void solveK1()
{
if (n == 1) return (void)printf("-1");
__int128 t1 = unp[1], t2;
if (t1 == 1) t2 = 2;
else if (t1 == n)
t2 = n - 1;
else if (p[t1 + 1] > p[t1 - 1])
t2 = t1 - 1;
else t2 = t1 + 1;
__int128 pt1 = p[t1], pt2 = p[t2], mpt = m / 2;
if (m % 2 == 1) ans = ((mpt) * (pt1 + pt2) + pt1) % mod;
else ans = ((mpt) * ((pt1 + pt2) % mod)) % mod;
write(ans);
}
inline void solve20()
{
__int128 tmp = 0, pos = 0;
for (__int128 i = 1; i <= n; i++)
{
tmp += f(unp[i], m);
if (tmp >= k)
{
ans += i;
ans %= mod;
pos = i;
break;
}
}
if (!pos) return (void) puts("-1");
dfs(m - 1, unp[pos], k - tmp + f(unp[pos], m));
write(ans);
}
inline void solve()
{
__int128 tmp = log2(k) + 1;
if(ksm(2, tmp) == k)
--tmp;
__int128 t1 = unp[1], t2;
if(t1 == 1)
t2 = 2;
else if(t1 == n)
t2 = n - 1;
else if(p[t1 + 1] > p[t1 - 1])
t2 = t1 - 1;
else t2 = t1 + 1;
ans = (((m - tmp) / 2) * (p[t1] + p[t2]) % mod + ((m - tmp) % 2) * p[t1] % mod) % mod;
__int128 st;
if((m - tmp) % 2)
st = t1;
else st = t2;
__int128 kk = 0;
bool flag = false;
for(__int128 i = 1; i <= n; ++i)
kk += f(i, tmp);
if(kk >= k)
flag = true;
if(!flag)
return (void)printf("-1");
dfs(tmp, st, k);
write(ans);
}
int main()
{
Base[0] = 1;
for(__int128 i = 1; i <= 70; ++i)
Base[i] = Base[i - 1] * 2;
n = read(), m = read(), k = read();
for(__int128 i = 1; i <= n; ++i)
{
p[i] = read();
unp[p[i]] = i;
}
if(k == 1) return solveK1(), 0;
__int128 tmp = log2(k) + 1;
if(ksm(2, tmp) == k)
--tmp;
if(m < tmp || m <= 70) return solve20(), 0;
return solve(), 0;
}
本文来自博客园,作者:Naitoah,转载请注明原文链接:https://www.cnblogs.com/LuckyCat-Naitoah/p/17553475.html

浙公网安备 33010602011771号