文章目录
1474D
题意
给出一段序列 a i a_i ai ,每次可以选择相邻的两个都不为 0 0 0的 a i a_i ai与 a i + 1 a_{i+1} ai+1,令其都 − 1 -1 −1。在操作之前,你可以使用一次特权:交换相邻的两个数的位置(只能使用一次)。问是否可以将序列全部变为0.
题解
那么就枚举这个特殊操作在哪里即可,这个时候我们就要O(1) 判断这个操作是否可行,首先要明白,如果没有这种特殊操作,那么我只需要从前往后判断或者从后往前是否可行即可,但是有这种操作了,枚举 i 和 i+1 ,那么我判断 i-1 往前和 i+2 往后都是可行的即可。然后再 O(1) 判断这个交换操作的可行性。
code
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
int pre[maxn], suffer[maxn], a[maxn];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n;
scanf("%d", &n);
for (int i = 0; i <= n + 10; i++)
pre[i] = suffer[i] = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if (a[i] >= pre[i - 1])
pre[i] = a[i] - pre[i - 1];
else
pre[i] = inf;
}
for (int i = n; i >= 1; i--)
{
if (a[i] >= suffer[i + 1])
suffer[i] = a[i] - suffer[i + 1];
else
suffer[i] = inf;
}
bool flag = false;
if (pre[n] == 0 || suffer[1] == 0)
flag = true;
for (int i = 1; i < n; i++)
{
if (pre[i] != inf && pre[i] == suffer[i + 1])
flag = true;
if (a[i + 1] >= pre[i - 1] && a[i] >= suffer[i + 2] && a[i + 1] - pre[i - 1] == a[i] - suffer[i + 2])
flag = true;
}
if (flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
1467D
题意
定义一条好的路径,当且仅当从任意点出发之后恰好经过了 k k k次移动,定义这条路径的权值为经过点权值 a i a_i ai 的总和,进行 q q q次修改,每次将第 a k a_k ak改为 x x x,询问此时所有‘好’路径的权值总和
题解
如果把每个位置在多少条路径上算出来,那么对于每次修改就比较好解决了。
d
p
[
i
]
[
k
]
dp[i][k]
dp[i][k]表示走了
k
k
k步当前在
i
i
i点的路径总数.对于一个点来说,枚举他作为中间点,枚举他作为第0步、1步、2步、3步的中间点,所以对于第i个点作为路径的总数显然是:
∑
k
=
0
k
=
m
d
p
[
i
]
[
k
]
∗
d
p
[
i
]
[
m
−
k
]
\sum_{k=0}^{k=m}dp[i][k]*dp[i][m-k]
∑k=0k=mdp[i][k]∗dp[i][m−k]
code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 1e9 + 7;
const ll maxn = 5009;
ll n, k, f[maxn][maxn], q, a[maxn], cnt[maxn], ans;
int main()
{
cin >> n >> k >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
f[0][i] = 1;
for (int i = 1; i <= k; i++)
for (int j = 1; j <= n; j++)
{
if (j == 1)
f[i][j] = f[i - 1][j + 1];
else if (j == n)
f[i][j] = f[i - 1][j - 1];
else
f[i][j] = (f[i - 1][j - 1] + f[i - 1][j + 1]) % mod;
}
for (int i = 1; i <= n; i++)
for (int j = 0; j <= k; j++)
cnt[i] = (cnt[i] + f[k - j][i] * f[j][i] % mod) % mod;
for (int i = 1; i <= n; i++)
ans = (ans + cnt[i] * a[i] % mod) % mod;
while (q--)
{
int i, x;
scanf("%lld%lld", &i, &x);
ans = (ans - cnt[i] * a[i] % mod) % mod;
a[i] = x;
ans = (ans + cnt[i] * a[i] % mod) % mod;
printf("%lld\n", (ans + mod) % mod);
}
}
1420E
题意
给你一个长度为 n 的 01序列,每一次操作你可以交换相邻的两个元素。
定义序列的 保护值( protection )为“序列中一对数值为 0 的数,且这对数之间夹着至少一个 1”的对数。
题解
a
n
s
=
n
(
n
−
1
)
2
−
c
n
t
1
(
c
n
t
1
−
1
)
2
−
c
n
t
1
(
n
−
c
n
t
1
)
−
∑
p
a
i
r
(
0
,
0
)
ans=\frac{n(n-1)}{2} - \frac{cnt_1(cnt_1-1)}{2}-cnt_1(n-cnt_1)-\sum_{}^{}pair(0,0)
ans=2n(n−1)−2cnt1(cnt1−1)−cnt1(n−cnt1)−∑pair(0,0)
d
p
[
i
]
[
j
]
[
k
]
表
示
安
排
好
前
i
个
1
,
且
第
i
个
1
位
于
位
置
j
,
使
用
k
次
操
作
后
的
最
小
0
对
。
dp[i][j][k]表示安排好前i个1,且第i个1位于位置j,使用k次操作后的最小0对。
dp[i][j][k]表示安排好前i个1,且第i个1位于位置j,使用k次操作后的最小0对。
长为k的连续0,贡献为
x
(
x
−
1
)
2
\frac{x(x - 1)}{2}
2x(x−1)
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105, M = 2e4 + 5, inf = 0x3f3f3f3f, mod = 1e9 + 7;
int n, c, sz, a[N], b[N];
ll dp[82][82][82 * 82];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]), c += a[i];
if (a[i])
b[++sz] = i;
}
ll ans = n * (n - 1) / 2 - c * (c - 1) / 2 - c * (n - c);
memset(dp, 0x3f, sizeof dp);
ll tmp = 0;
for (int i = 0; i <= sz; i++)
{
if (i)
tmp += max(0, (b[i] - b[i - 1] - 1) * (b[i] - b[i - 1] - 2) / 2);
dp[i][b[i]][0] = tmp;
}
for (int i = 1; i <= sz; i++)
for (int j = 1; j <= n; j++)
for (int k = 0; k < j; k++)
for (int l = 0; l <= n * (n - 1) / 2; l++)
dp[i][j][abs(j - b[i]) + l] = min(dp[i][j][abs(j - b[i]) + l], dp[i - 1][k][l] + max(0, (j - k - 1) * (j - k - 2) / 2));
ll mn = 1e18;
for (int i = 0; i <= n * (n - 1) / 2; i++)
{
for (int j = 1; j <= n; j++)
{
mn = min(mn, dp[sz][j][i] + max(0, (n - j) * (n - j - 1) / 2));
}
if (!sz)
mn = ans;
printf("%lld ", ans - mn);
}
}
1425D
题意
1000*1000的矩阵中n条蛇,你有m个武器可以攻击蛇,武器只能放在有蛇的地方,每个武器有r的攻击距离,对于每种可能的武器放置方案,产生的攻击值是所有蛇的攻击力的和的平方,求所有方案的攻击值的和。
题解
每次枚举两条蛇,计算包含这两条蛇的方案数,这两条蛇产生的贡献是总方案数*两条蛇的攻击力的积。包含两条蛇的总方案数 = 所有的方案数-不包含第一条蛇的方案,-不包含第二条蛇的方案+不包含两条蛇的方案。就是一个简单的容斥。
不包含某条蛇的方案=C(蛇的总数-可以攻击的到这条蛇的武器的数量,m)
不包含某两条蛇的方案=C(蛇的总数-只能攻击到其中一条蛇武器是数量+可以同时攻击到两条蛇的数量)。
求可能攻击到的某条蛇的武器的数量用二维前缀和维护一下就可以,注意可能不存在能同时攻击到两条蛇的武器,发现两个矩阵没有交点的时侯直接返回0.
code
#include <bits/stdc++.h>
using namespace std;
using BS = bitset<2048>;
const int kMod = 1e9 + 7;
int main()
{
int m, n, r;
cin >> n >> m >> r;
vector<int> x(n), y(n), b(n);
for (int i = 0; i < n; ++i)
{
cin >> x[i] >> y[i] >> b[i];
}
vector<BS> forbid(n);
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
int dx = x[i] - x[j], dy = y[i] - y[j];
int d = max(abs(dx), abs(dy));
if (d <= r)
forbid[i][j] = true;
}
}
vector<vector<int>> C(n + 1, vector<int>(n + 1, 0));
for (int i = 0; i <= n; ++i)
{
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; ++j)
{
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
if (C[i][j] >= kMod)
C[i][j] -= kMod;
}
}
int total = 0;
vector<int> coefs(n);
for (int i = 0; i < n; ++i)
{
int cnt = forbid[i].count();
int coef = (C[n][m] + kMod - C[n - cnt][m]) % kMod;
coefs[i] = coef;
total += 1LL * b[i] * b[i] % kMod * coef % kMod;
if (total >= kMod)
total -= kMod;
}
for (int i = 0; i < n; ++i)
{
for (int j = i + 1; j < n; ++j)
{
int cnt = (forbid[i] | forbid[j]).count();
int coef = (C[n][m] - C[n - cnt][m] + kMod) % kMod;
coef = (coefs[i] + coefs[j] - coef) % kMod;
if (coef < 0)
coef += kMod;
total += 2LL * b[i] * b[j] % kMod * coef % kMod;
if (total >= kMod)
total -= kMod;
}
}
cout << total << endl;
return 0;
}
1353F
题意
给定一个n∗m的地图,每个格子有初始高度。只能向右或向下移动,且要求第i+1步的高度必须比第i步恰好高1。每次操作可以使得任意一个格子的高度减1。问最少需要几次操作,使得地图中存在由(1,1)到(n,m)的路径。
题解
满足题意的路径中一定经过一个不需要降低高度的格子。以这个格子的高度为基准,计算可能经过的格子的花销,更新dp数组。
令dp[i][j]表示从(1,1)到(i,j)所需要的最小操作数
转移方程:
dp[i][j]=min(dp[i−1][j]+cost,dp[i][j−1]+cost)
而这个花销可以这样计算:
因为只能向右或向下移动,说明相邻两步移动的坐标差恰为1,又高度差也恰为1,所以:设这个基准高度为H(x,y),由题可知H(x,y)与任意格子的应有高度H(i,j)存在以下关系:
H(i,j)=H(x,y)−(x−i+y−j)
所以做法是先通过遍历选定基准格子(x,y),再计算得出可能路径上的格子的应有高度,据此再计算花销。
也可以通过H(x,y)计算H(1,1),再以H(1,1)计算各格子的应有高度,此时式子可简化为:
H(i,j)=H(1,1)+i+j−2
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 1e18;
const int maxn = 100 + 10;
LL Map[maxn][maxn], dp[maxn][maxn];
int n, m;
LL solve(LL begin)
{
for (int i = 0; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
dp[i][j] = INF;
}
}
dp[1][1] = Map[1][1] - begin;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (i == 1 && j == 1)
continue;
LL nowh = begin + i + j - 2;
if (Map[i][j] < nowh)
continue;
LL cost = Map[i][j] - nowh;
dp[i][j] = min(dp[i - 1][j] + cost, dp[i][j - 1] + cost);
}
}
return dp[n][m];
}
int main()
{
int t;
cin >> t;
while (t--)
{
LL ans = INF;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> Map[i][j];
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
LL begin = Map[i][j] - i - j + 2;
if (begin <= Map[1][1])
ans = min(ans, solve(begin));
}
}
cout << ans << endl;
}
}
1304 F1
题意
题解
定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为只考虑原矩阵有 i × m i×m i×m个元素的时候,选择第i行的第j个元素作为第i行的矩阵块左上角元素时,覆盖数之和最大值。
也就是说 a n s = m a x ( d p [ n ] [ j ] ) ans=max(dp[n][j]) ans=max(dp[n][j])
code
#include <bits/stdc++.h>
#define MAXN 55
#define MAXM 20005
using namespace std;
struct node
{
int le, ri;
int mx, tag;
} sgt[MAXM << 2];
int n, m, k;
int a[MAXN][MAXM], sum[MAXN][MAXM], dp[MAXN][MAXM];
void build(int cur, int l, int r, int x)
{
sgt[cur].le = l, sgt[cur].ri = r;
sgt[cur].tag = 0;
if (l == r - 1)
{
sgt[cur].mx = dp[x - 1][l] + sum[x][l + k - 1] - sum[x][max(k - 1, l - 1)];
}
else
{
int le = cur << 1, ri = le + 1;
build(le, l, (l + r) >> 1, x);
build(ri, (l + r) >> 1, r, x);
sgt[cur].mx = max(sgt[le].mx, sgt[ri].mx);
}
}
void update(int cur)
{
int le = cur << 1, ri = le + 1;
sgt[le].mx += sgt[cur].tag;
sgt[ri].mx += sgt[cur].tag;
sgt[le].tag += sgt[cur].tag;
sgt[ri].tag += sgt[cur].tag;
sgt[cur].tag = 0;
}
void modify(int cur, int l, int r, int del)
{
if (l <= sgt[cur].le && sgt[cur].ri <= r)
{
sgt[cur].mx += del;
sgt[cur].tag += del;
}
else
{
if (sgt[cur].tag)
update(cur);
int le = cur << 1, ri = le + 1;
int mid = (sgt[cur].le + sgt[cur].ri) >> 1;
if (l < mid)
modify(le, l, r, del);
if (r > mid)
modify(ri, l, r, del);
sgt[cur].mx = max(sgt[le].mx, sgt[ri].mx);
}
}
void solve()
{
scanf("%d %d %d", &n, &m, &k);
for (int i = 1; i <= n; i++)
{
sum[i][0] = 0;
for (int j = 1; j <= m; j++)
{
scanf("%d", &a[i][j]);
sum[i][j] = sum[i][j - 1] + a[i][j];
}
}
for (int j = 1; j <= m - k + 1; j++)
dp[1][j] = sum[1][j + k - 1] - sum[1][j - 1];
for (int i = 2; i <= n; i++)
{
build(1, 1, m - k + 2, i);
for (int j = 1; j <= m - k + 1; j++)
{
modify(1, j, j + k, -a[i][j + k - 1]);
dp[i][j] = sgt[1].mx + sum[i][j + k - 1] - sum[i][j - 1];
modify(1, max(j - k + 1, 1), j + 1, a[i][j]);
}
}
int ans = dp[n][1];
for (int j = 2; j <= m; j++)
ans = max(ans, dp[n][j]);
printf("%d\n", ans);
}
int main()
{
int T = 1;
// scanf("%d", &T);
while (T--)
{
solve();
}
return 0;
}
1396 C
题意
有 n 层关卡,每层有 ai 个小怪(1 血)和 1 个老怪(2 血)。有三种武器:1 武器每次攻击耗时 r1,可以攻击一个怪 1 血;2 武器每次攻击耗时 r2,可以攻击一层每个怪 1 血;3 武器每次攻击耗时 r3,可以杀死一个怪。当一次攻击伤害了老怪但是没有杀死他时,玩家会被迫移动至相邻的层;也可以主动移至相邻的层。刚开始时在 1 层,每次移动耗时 d,求最后杀死所有怪的最少耗时(不一定要在 n 层结束)。
题解
首先很明显,打每一层都有两种打法:
分次打,先用 2 或 1 把 boss 打残,把小兵都打死,然后到时候回来补一刀(用 1)。
s
t
i
=
m
i
n
(
r
2
,
r
1
(
a
i
+
1
)
)
+
r
1
sti=min(r2,r1(ai+1))+r1
sti=min(r2,r1(ai+1))+r1
一次打掉,用 1 把 ai 个依次打掉,然后用 3 把 boss 干掉。
pai=r1ai+r3
除了 sti 和 pai,剩下可以对答案产生贡献的就是如何走位(d 的贡献)。
假设每个每层如何打已经决定好,且下文中的分界点一定,可以证明如下走位最优:
对于某个分界点后一段选 st 的,可以到达终点后回来打完(详见样例 #1 解释)。
对于分界点前一段的,从 1 出发:
对于每两个相邻的选 st 的层对 a,b,走 a→b→a→b 的途中将两层打完;
对于多余的选 st 的层 i,走 i→i±1→i。
对于选 pa 的,直接走过就可以。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 0x3f3f3f3f3f3f3f3f;
const int N = 1000000;
int n;
int r1, r2, r3, d;
int a[N + 1];
int dp[N + 1];
int x[N + 1], y[N + 1];
int Sum[N + 1];
signed main()
{
cin >> n >> r1 >> r2 >> r3 >> d;
for (int i = 1; i <= n; i++)
scanf("%lld", a + i);
for (int i = 1; i <= n; i++)
{
x[i] = a[i] * min(r1, r3) + r3;
y[i] = min(r2 + min(min(r1, r2), r3), a[i] * min(r1, r3) + min(r1, r2) + min(min(r1, r2), r3));
Sum[i] = Sum[i - 1] + min(x[i], y[i]);
}
int mn = inf;
dp[1] = x[1];
for (int i = 2; i < n; i++)
{
mn = min(mn, dp[i - 2] - Sum[i - 2] - 2 * (i - 1) * d);
dp[i] = min(dp[i - 1] + x[i], mn + Sum[i] + 2 * i * d);
}
dp[n] = dp[n - 1] + x[n];
for (int i = 0; i <= n - 2; i++)
dp[n] = min(dp[n], dp[i] + Sum[n] - Sum[i] + 2 * n * d - 2 * (i + 1) * d),
dp[n] = min(dp[n], dp[i] + Sum[n - 1] - Sum[i] + x[n] + n * d - (i + 1) * d);
cout << dp[n] + (n - 1) * d;
return 0;
}
1238E
题意
给出字符串为m个的串,现在你将m个字符进行排列成一行,依次敲入这个串的每个字符,问手指移动的长度的最小值。m<=20。
题解
考虑一个一个排放字符,对于放在第一个位置的字符,第二个字符距离1,第三个距离2。用状压来表示前面已经放置好的字符。每放一个,对于已经放的和没有放的字符追加一次贡献。做下来后你会发现,第一个字符对第i个字符的贡献恰好是i-1。
code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7, M = 20;
int n, m, cnt[M][M];
int pos[(1 << M) + 7], f[(1 << M) + 7], g[M][(1 << M) + 7], h[(1 << M) + 7];
char s[N];
vector<int> V[M];
int main()
{
cin >> n >> m;
scanf("%s", s + 1);
for (int i = 1; i < n; i++)
cnt[s[i] - 'a'][s[i + 1] - 'a']++,
cnt[s[i + 1] - 'a'][s[i] - 'a']++;
for (int i = 0; i < m; i++)
pos[1 << i] = i;
memset(f, 0x3f, sizeof(f));
int mx = (1 << m) - 1;
f[0] = 0;
for (int p = 0; p < m; p++)
for (int o = 1; o < mx; o++)
{
if (o & (1 << p))
continue;
g[p][o] = g[p][o - (o & -o)] + cnt[p][pos[o & -o]];
}
for (int o = 0; o < mx; o++)
for (int p = 0; p < m; p++)
if (o & (1 << p))
h[o] += g[p][mx ^ o];
for (int o = 0; o < mx; o++)
for (int p = 0; p < m; p++)
{
if (o & (1 << p))
continue;
f[o ^ (1 << p)] = min(f[o ^ (1 << p)], f[o] + h[o]);
}
printf("%d\n", f[mx]);
return 0;
}
1468A 2021.2.9
题意
给定数组 a i a_i ai,求最长几乎上升子数组长度。
一个有 k k k个数的数组 b i b_i bi如果满足
m
i
n
(
b
1
,
b
2
)
≤
m
i
n
(
b
2
,
b
3
)
≤
m
i
n
(
b
3
,
b
4
)
≤
.
.
.
≤
m
i
n
(
b
k
−
1
,
b
k
)
min(b_1,b_2)≤min(b_2,b3)≤min(b_3,b_4)≤...≤min(b_{k−1},b_k)
min(b1,b2)≤min(b2,b3)≤min(b3,b4)≤...≤min(bk−1,bk)
则数组
b
b
b成为几乎上升数组。
题解
在最长上升子序列的基础商,最长上升子序列允许中间忽然插入一个很大的数。
于是,对于第i个数ai,它可以从前面小于ai的aj直接转移过来,或者在(j,i)中插一个数较大的数ak。
设dp1[i]表示以i结尾的最长几乎上升子序列的最大长度,dp2[i]表示以第i个数结尾的最长几乎上升子序列的最大长度。
从左到右遍历,对于第i个数ai
d
p
1
[
a
i
]
=
m
a
x
x
≤
a
i
(
d
p
1
[
x
]
)
+
1
dp1[ai]=maxx≤ai(dp1[x])+1
dp1[ai]=maxx≤ai(dp1[x])+1
d
p
1
[
a
i
]
=
m
a
x
x
≤
a
i
且
r
p
o
s
x
<
i
(
d
p
1
[
x
]
)
+
2
dp1[ai]=max_{x≤ai且rposx<i}(dp1[x])+2
dp1[ai]=maxx≤ai且rposx<i(dp1[x])+2
其中ri表示说位置i右边第一个比它大的数的位置,这个可以事先用单调栈预处理。
posx表示这个数x的位置。
code
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MAXN = 5e5 + 10;
const int inf = 1e9 + 7;
typedef long long LL; //多次踩坑之后已经可以放心使用
struct Segtree
{
struct node
{
int l, r;
int mx, lazy;
} tree[MAXN << 2];
int a[MAXN]; //you can put the num to this first
void push_down(int i)
{
if (tree[i].lazy != 0)
{
tree[i * 2].lazy = tree[i].lazy;
tree[i * 2 + 1].lazy = tree[i].lazy;
// int mid = (tree[i].l + tree[i].r) >> 1;
tree[i * 2].mx = tree[i].lazy;
tree[i * 2 + 1].mx = tree[i].lazy;
tree[i].lazy = 0;
}
}
void build(int i, int l, int r)
{
tree[i].l = l;
tree[i].r = r;
tree[i].lazy = 0;
if (l == r)
{
tree[i].mx = a[l];
return;
}
int mid = (l + r) >> 1;
build(i * 2, l, mid);
build(i * 2 + 1, mid + 1, r);
tree[i].mx = std::max(tree[i * 2].mx, tree[i * 2 + 1].mx);
}
void add(int i, int l, int r, int k)
{
if (tree[i].l >= l && tree[i].r <= r)
{
tree[i].mx = k;
tree[i].lazy = k;
return;
}
if (tree[i].r < l || tree[i].l > r)
return;
push_down(i);
if (tree[i * 2].r >= l)
add(i * 2, l, r, k);
if (tree[i * 2 + 1].l <= r)
add(i * 2 + 1, l, r, k);
tree[i].mx = std::max(tree[i * 2].mx, tree[i * 2 + 1].mx);
}
int search(int i, int l, int r)
{
if (tree[i].l >= l && tree[i].r <= r)
{
return tree[i].mx;
}
if (tree[i].r < l || tree[i].l > r)
return 0;
push_down(i);
int ans = -inf;
if (tree[i * 2].r >= l)
ans = std::max(ans, search(i * 2, l, r));
if (tree[i * 2 + 1].l <= r)
ans = std::max(ans, search(i * 2 + 1, l, r));
return ans;
}
} tr1, tr2;
// int n;
int a[MAXN], r[MAXN];
int dp[2][MAXN], id[MAXN];
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int n;
scanf("%d", &n);
stack<int> st;
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
// tree[0][i] = tree[1][i] = 0;
tr1.a[i] = tr2.a[i] = 0;
while (!st.empty() && a[st.top()] <= a[i])
{
r[st.top()] = i;
st.pop();
}
st.push(i);
id[i] = i;
dp[0][i] = dp[1][i] = 0;
}
tr1.build(1, 1, n);
tr2.build(1, 1, n);
while (!st.empty())
{
r[st.top()] = n + 1;
st.pop();
}
sort(id + 1, id + n + 1, [&](int x, int y) {
return r[x] < r[y];
});
dp[0][a[1]] = dp[1][1] = 1;
int cur = 1;
for (int i = 2; i <= n; i++)
{
dp[0][a[i]] = max(1, max(tr1.search(1, 1, a[i]) + 1, tr2.search(1, 1, a[i]) + 2));
dp[1][i] = dp[0][a[i]];
while (cur <= n && r[id[cur]] <= i)
{
tr2.add(1, a[id[cur]], a[id[cur]], dp[1][id[cur]]);
cur++;
}
tr1.add(1, a[i], a[i], dp[0][a[i]]);
}
int ans = 1;
for (int i = 1; i <= n; i++)
{
ans = max(ans, dp[0][i]);
}
// cout << ans << "\n";
printf("%d\n", ans);
}
}
1268B
题意
给一个不完整的棋盘,每一列的方格数单调非升,问最多可以用多少个不重叠的1x2或者2x1的矩形覆盖棋盘(可以不完全覆盖)
题解
这种问题可以看成二分图匹配,相邻格子染色不同,假设分别染成黑白两种颜色,那么很明显想要用一个矩形覆盖,就必须要有一个黑格和一个白格匹配(两者相邻)。最大匹配数就是最终的答案。
但是这个数据范围过大,不能用匈牙利或者网络流解决。
注意到棋盘每一列的方格数是单调非升的,于是很明显只要先进行染色处理,然后从黑白格子中选出数量最少的那一种,其个数就是最大匹配数。
code
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 10;
int a[MAXN];
int main()
{
int n;
scanf("%d", &n);
long long b = 0, w = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
if (i & 1)
{
b += a[i] / 2;
w += (a[i] + 1) / 2;
}
else
{
b += (a[i] + 1) / 2;
w += a[i] / 2;
}
}
printf("%lld\n", min(b, w));
// cin >> n;
}
597div2 F 2021.2.10
题意
给定一个区间 [ l , r ] [l,r] [l,r],找到满足 a + b = a ⊕ b a + b = a \oplus b a+b=a⊕b的个数
题解
数位dp
code
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 33;
using LL = long long;
LL dp[MAXN][2][2];
int a[MAXN], b[MAXN];
LL dfs(int pos, int flag1, int flag2)
{
if (pos == 0)
return 1;
if (dp[pos][flag1][flag2] != -1)
return dp[pos][flag1][flag2];
int up1 = flag1 ? a[pos] : 1;
int up2 = flag2 ? b[pos] : 1;
LL ans = 0;
for (int i = 0; i <= up1; i++)
{
for (int j = 0; j <= up2; j++)
{
if ((i & j) == 0)
{
ans += dfs(pos - 1, flag1 & (i == up1), flag2 & (j == up2));
}
}
}
// if (flag1 == 0 && flag2 == 0)
dp[pos][flag1][flag2] = ans;
return ans;
}
LL cal(int x, int y)
{
int len = 0;
memset(dp, -1, sizeof dp);
memset(a, 0, sizeof a);
memset(b, 0, sizeof b);
while (x)
{
a[++len] = x % 2;
x = x / 2;
}
len = 0;
while (y)
{
b[++len] = y % 2;
// y >>= 1;
y = y / 2;
}
return dfs(30, 1, 1);
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int x, y;
scanf("%d %d", &x, &y);
printf("%lld\n", cal(y, y) - 2ll * cal(x - 1, y) + cal(x - 1, x - 1));
}
// cin >> t;
}
1407 D
题意

题解
用两个单调栈维护后两个条件。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5 + 7;
int n, m, s[N], dp[N], atot, a[N], btot, b[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &s[i]);
memset(dp, 0x3f, sizeof(dp));
a[++atot] = 1, b[++btot] = 1, dp[1] = 0;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + 1;
while(atot && s[i] >= s[a[atot]]) {
if(s[i] != s[a[atot]]) dp[i] = min(dp[i], dp[a[atot - 1]] + 1);
--atot;
}
while(btot && s[i] <= s[b[btot]]) {
if(s[i] != s[b[btot]]) dp[i] = min(dp[i], dp[b[btot - 1]] + 1);
--btot;
}
a[++atot] = i, b[++btot] = i;
}
printf("%d\n", dp[n]);
return 0;
}
gym 102920 I 2021.2.12
题意
给一个数组a,多个询问[S, E, U],问se中,小于u的最大子段和
题解
n^2预处理所有的区间的和,把询问按照val进行排序,区间也按照u排序,一边询问一边加,用二维树状数组维护
code
#include <bits/stdc++.h>
#pragma GCC optimize(3, "Ofast", "inline")
#define ll long long
#define maxn 2100
using namespace std;
ll a[2020];
ll ans[200010];
struct cv
{
int l, r;
ll u;
int id;
inline bool operator<(const cv &b) { return u < b.u; }
} b[200010];
const ll inf = -0x3f3f3f3f3f3f3f3f;
int n;
ll tr[maxn][maxn];
cv v[maxn * maxn];
inline int lowbit(int x) { return x & -x; }
inline void cgx(int x, int y, ll val)
{
for (int i = x; i; i -= lowbit(i))
{
for (int j = y; j < maxn; j += lowbit(j))
tr[i][j] = max(tr[i][j], val);
}
}
inline ll query(int x, int y)
{
ll ans = inf;
for (int i = x; i < maxn; i += lowbit(i))
{
for (int j = y; j; j -= lowbit(j))
ans = max(ans, tr[i][j]);
}
return ans;
}
int main()
{
int m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
a[i] += a[i - 1];
}
for (int i = 0; i < m; i++)
{
b[i].id = i;
scanf("%d%d%lld", &b[i].l, &b[i].r, &b[i].u);
}
memset(tr, -0x3f, sizeof(tr));
int k = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
cv tp;
tp.l = i, tp.r = j, tp.u = a[j] - a[i - 1];
v[k++] = tp;
}
}
sort(b, b + m);
sort(v, v + k);
int cnt = 0;
for (int i = 0; i < m; i++)
{
while (cnt < k)
{
if (v[cnt].u > b[i].u)
break;
cgx(v[cnt].l, v[cnt].r, v[cnt].u);
cnt++;
}
ans[b[i].id] = query(b[i].l, b[i].r);
}
for (int i = 0; i < m; i++)
{
if (ans[i] < -1e16)
printf("NONE\n");
else
printf("%lld\n", ans[i]);
}
return 0;
}
990F 2021.2.13
题意
https://codeforces.com/contest/990/problem/F
题解
计算它子树内部会有多少权值需要转移,然后沿这条边转移
code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
inline int read()
{
char ch = getchar();
int res = 0, f = 1;
while (!isdigit(ch))
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch))
res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
return res * f;
}
const int N = 200005, M = 300005;
int n, m, adj[N], nxt[M << 1], to[M << 1], pos[M << 1], cnt = 1;
long long tot, ans[M], a[N], val[M << 1];
bool vis[N];
inline void addedge(int u, int v, int i)
{
nxt[++cnt] = adj[u], adj[u] = cnt, to[cnt] = v, pos[cnt] = i;
nxt[++cnt] = adj[v], adj[v] = cnt, to[cnt] = u, pos[cnt] = i;
}
inline int dfs(int u, int fa)
{
vis[u] = true;
for (int e = adj[u]; e; e = nxt[e])
{
int v = to[e];
if (vis[v] || v == fa)
continue;
dfs(v, u);
a[u] += a[v];
val[e ^ 1] += a[v];
}
}
int main()
{
n = read();
for (int i = 1; i <= n; i++)
a[i] = read(), tot += a[i];
m = read();
for (int i = 1; i <= m; i++)
{
int u = read(), v = read();
addedge(u, v, i);
}
if (tot != 0)
{
cout << "Impossible" << '\n';
return 0;
}
else
cout << "Possible" << '\n';
dfs(1, 0);
for (int i = 1; i <= cnt; i++)
{
if (i & 1)
ans[pos[i]] += val[i];
else
ans[pos[i]] -= val[i];
}
for (int i = 1; i <= m; i++)
{
cout << (ans[i]) << '\n';
}
}
990D
题意
给定a,b.要求构造一个邻接矩阵,对应的图中,有a个联通块;对应的补图有b个联通块。
题解
a>1,那么b一定为1.说明a,b中至少有一个是1.特判构造
a>1,前a-1个独立,为a-1个联通块;[a+1,n]顶点为一个联通块
a==1,b>1.构造补图的,再返回来
a1,b1.n=1成立,n=2,n=3不成立.n>=4,就是一个链
code
#include <bits/stdc++.h>
using namespace std;
int a, b, n, res[1005][1005];
int main()
{
cin >> n >> a >> b;
if (n == 1)
return cout << "YES\n0", 0;
if (min(a, b) > 1 || (n <= 3 && max(a, b) == 1))
return cout << "NO", 0;
cout << "YES\n";
for (int i = 1 + max(a, b); i <= n; ++i)
res[i - 1][i] = res[i][i - 1] = 1;
for (int i = 1; i <= n; ++i, cout << '\n')
for (int j = 1; j <= n; ++j)
cout << (i == j ? 0 : res[i][j] ^ (a < b));
}
1303 E 2021.2.15
题意
给定串s和t,可以做的操作是,将s的子串,加入到一个p的末尾,p在开始时是空串,可以进行多次操作,问能不能让p变成t
题解
将字符串t分为t1和t2,dp[i][j]为凑成t1的前i个和t2的前j个需要的s的最小长度。dp[i][j]从dp[i - 1][j]和dp[i][j-1]转移
code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 410;
string s, t;
int n, m;
int nxt[maxn][30];
int dp[maxn][maxn];
void nxt_init()
{
for (int i = 0; i <= 25; i++)
nxt[n][i] = n + 1;
for (int i = n; i >= 1; i--)
{
for (int j = 0; j <= 25; j++)
nxt[i - 1][j] = nxt[i][j];
nxt[i - 1][s[i - 1] - 'a'] = i;
}
}
bool judge(string t1, string t2)
{
int len1 = t1.length(), len2 = t2.length();
dp[0][0] = 0;
for (int i = 0; i <= len1; i++)
{
for (int j = 0; j <= len2; j++)
{
if (!i && !j)
continue;
dp[i][j] = n + 1;
if (i && dp[i - 1][j] < n)
dp[i][j] = min(dp[i][j], nxt[dp[i - 1][j]][t1[i - 1] - 'a']);
if (j && dp[i][j - 1] < n)
dp[i][j] = min(dp[i][j], nxt[dp[i][j - 1]][t2[j - 1] - 'a']);
}
}
return dp[len1][len2] <= n;
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> s >> t;
bool flag = 0;
m = t.length();
n = s.length();
nxt_init();
for (int i = 0; i < m; i++)
{
if (judge(t.substr(0, i), t.substr(i)))
{
flag = 1;
break;
}
}
if (flag)
puts("YES");
else
puts("NO");
}
}
1481 E
题意
书架上有n本书,每本书有自己的颜色ai,现在有一个操作是将一本书取出并放在书架的最右边。问最少需要多少次操作可以使颜色相同的书放在一起。
题解
移动的很难考虑,那换个角度不防考虑最大化不移动书的数量。设f[i]表示i~n这些书中最大的不移动的数量。
1.如果i这个地方的书要动的话那么很简单f[i]=f[i+1]。
2.如果i这个地方不动的话,那么就是让i~n中所有颜色为a[i]的书都不动,动其他书。用R[x]表示颜色为x的书的最右边的那一个位置,L[x]为最左边的。那么显然 i ~ R[a[i]]之间其他颜色的书都是要动的,因为要让他们合并到一起,那么R[i]右边的书是否要动呢。我们可以想一下,当i != L[a[i]] 说明当前位置的左边还存在这个颜色的书,那要使他们合并,说明L[a[i]] ~ i中相同颜色的书其实是要移动到后面来的,所以R[i]右边的书也是需要移动的。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N];
int l[N], r[N], f[N], num[N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
if (!l[a[i]])
l[a[i]] = i;
r[a[i]] = i;
}
f[n + 1] = 0;
for (int i = n; i >= 1; i--)
{
num[a[i]]++;
f[i] = f[i + 1];
if (i == l[a[i]])
f[i] = max(f[i], num[a[i]] + f[r[a[i]] + 1]);
else
f[i] = max(f[i], num[a[i]]);
}
printf("%d\n", n - f[1]);
//system("pause");
return 0;
}
1399F
题意
https://codeforces.com/problemset/problem/1399/F
题解
求出f[i]为第i个区间内部加本身能放下满足要求的区间总数,最后增加一个mini-max的区间,就把整个问题转换成了这整个嵌套最多能多少。
肯定是把长度小的套进长度大的,所以我们先把所有区间按长度排个序
然后把被当前要计算f[i]的区间包含的所有区间拿出来对右端点排序,也可以直接丢进右端点下标的vector里面
然后从左到右扫这个区间,最多可以放多少个不相交的区间,但区间的值要改成这个区间本身加上套在他里面的区间总数f[j],因为这个子区间更短,所以f[j]已知了
code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int maxl = 3e5 + 10;
int n, m, cas, k, cnt, tot, ans;
struct node
{
int l, r, len, id;
} a[maxl];
vector<node> b[maxl];
int num[maxl], f[maxl], dp[maxl];
char s[maxl];
bool in[maxl];
inline bool cmplen(const node &a, const node &b)
{
return a.len < b.len;
}
inline void prework()
{
scanf("%d", &n);
tot = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &a[i].l, &a[i].r), a[i].id = i;
num[++tot] = a[i].l;
num[++tot] = a[i].r;
}
sort(num + 1, num + 1 + tot);
tot = unique(num + 1, num + 1 + tot) - num - 1;
for (int i = 1; i <= n; i++)
{
a[i].l = lower_bound(num + 1, num + 1 + tot, a[i].l) - num;
a[i].r = lower_bound(num + 1, num + 1 + tot, a[i].r) - num;
a[i].len = a[i].r - a[i].l + 1;
}
}
inline void mainwork()
{
++n;
a[n] = node{1, tot, tot, n};
sort(a + 1, a + 1 + n, cmplen);
for (int i = 1; i <= n; i++)
{
for (int j = a[i].l - 1; j <= a[i].r; j++)
dp[j] = 0, b[j].clear();
for (int j = 1; j < i; j++)
if (a[i].l <= a[j].l && a[j].r <= a[i].r)
b[a[j].r].push_back(a[j]);
for (int j = a[i].l; j <= a[i].r; j++)
{
dp[j] = dp[j - 1];
for (node d : b[j])
dp[j] = max(dp[d.l - 1] + f[d.id], dp[j]);
}
f[a[i].id] = dp[a[i].r] + 1;
}
}
inline void print()
{
printf("%d\n", f[n] - 1);
}
int main()
{
int t = 1;
scanf("%d", &t);
for (cas = 1; cas <= t; cas++)
{
prework();
mainwork();
print();
}
return 0;
}
701div2 F 2021.2.16
题意
给出一个b数组,问有多少个a数组满足,要么 a i = b i a_i=b_i ai=bi要么 b i = ∑ 1 i a i b_i=\sum_{1}^{i}a_i bi=∑1iai
题解
可以考虑dp,
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]代表区间[1,n]的前缀和为j共有几种方案
转移1,a[i] = b[i]:
d
p
[
i
]
[
j
]
=
d
p
[
i
]
[
j
−
b
[
i
]
]
dp[i][j] = dp[i][j - b[i]]
dp[i][j]=dp[i][j−b[i]]
转移2,b[i]是前缀和:
d
p
[
i
]
[
b
[
i
]
]
=
∑
−
i
n
f
i
n
f
d
p
[
i
]
[
j
]
dp[i][b[i]]=\sum_{-inf}^{inf}dp[i][j]
dp[i][b[i]]=∑−infinfdp[i][j]
用水位线(https://codeforces.com/blog/entry/58316)优化dp
code
#include <bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template <class T>
void read(T &x)
{
T res = 0, f = 1;
char c = getchar();
while (!isdigit(c))
{
if (c == '-')
f = -1;
c = getchar();
}
while (isdigit(c))
{
res = (res << 3) + (res << 1) + c - '0';
c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, n, b[N];
map<int, int> mp;
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(t);
while (t--)
{
int wel = 0, tol = 0;
read(n);
mp.clear();
mp[0] = 1, tol = 1;
for (int i = 1; i <= n; i++)
read(b[i]);
for (int i = 1; i <= n; i++)
{
wel -= b[i];
int change = tol - mp[b[i] + wel];
mp[b[i] + wel] = tol;
tol = (tol + change) % mod;
}
printf("%lld\n", (tol % mod + mod) % mod);
}
return 0;
}
923B
题意
https://codeforces.com/contest/923/problem/B
题解
用优先队列配合水位线一次性扫完
code
#include <cstdio>
#include <algorithm>
#include <queue>
#define ll long long
#define N 100010
using namespace std;
priority_queue<ll, vector<ll>, greater<ll>> q;
int n, A[N], T[N];
ll sum[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &A[i]);
for (int i = 1, t; i <= n; ++i)
{
scanf("%d", &T[i]);
sum[i] = sum[i - 1] + T[i];
}
for (int i = 1; i <= n; ++i)
{
ll Ans = 0;
q.push(A[i] + sum[i - 1]);
while (!q.empty() && q.top() <= sum[i])
{
Ans += q.top() - sum[i - 1];
q.pop();
}
Ans += q.size() * 1ll * T[i];
printf("%I64d ", Ans);
}
return 0;
}
1257E 2021.2.17
题意
有三个人,第一个人只想要前缀(1,2,3,。。。。)第三个人只想要后缀(。。。n - 2, n - 1, n)第二个人只想要他们剩下的,根据现在三个人已经有的数,问最小的移动步数符合上述条件
题解
在第一个人中出现的数标记为a[x] = 1,第二个a[x]=2第三个a[x]=3,ans = k1+k2+k3-a的最长不下降子序列
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 2e5 + 10;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
int a[MAXN];
int main()
{
io();
int k[4];
cin >> k[1] >> k[2] >> k[3];
for (int i = 1; i <= k[1]; i++)
{
// cin >> a[i];
int x;
cin >> x;
a[x] = 1;
}
for (int i = 1; i <= k[2]; i++)
{
int x;
cin >> x;
a[x] = 2;
}
for (int i = 1; i <= k[3]; i++)
{
int x;
cin >> x;
a[x] = 3;
}
vector<int> vc;
for (int i = 1; i <= k[1] + k[2] + k[3]; i++)
{
if (!vc.empty() && vc.back() <= a[i])
{
vc.push_back(a[i]);
}else
{
if (vc.empty())
{
vc.push_back(a[i]);
continue;
}
int pos = upper_bound(vc.begin(), vc.end(), a[i]) - vc.begin();
if (vc[pos] > a[i])
vc[pos] = a[i];
}
}
cout << (k[1] + k[2] + k[3]) - vc.size() << "\n";
// cin >> k[1];
}
1486E 2020.2.19
详细题解
https://blog.csdn.net/weixin_45697774/article/details/113856213
分析
主要是找到数据范围中的w最多只有50,建立虚点,这是问题的关键
code
#include <bits/stdc++.h>
using namespace std;
typedef int LL;
const int MAXN = 5e6 + 10;
const int inf = 1e9 + 7;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
struct Node
{
LL dis, v;
bool operator<(const Node b) const
{
return dis > b.dis;
}
};
bool vis[MAXN];
priority_queue<Node> Q;
vector<Node> edg[MAXN];
LL d[MAXN];
void Dijkstra(LL d[], LL s)
{
while (!Q.empty())
Q.pop();
memset(vis, 0, sizeof vis);
d[s] = 0;
Q.push((Node){0, s});
while (!Q.empty())
{
Node x = Q.top();
Q.pop();
if (vis[x.v])
continue;
vis[x.v] = 1;
for (auto v : edg[x.v])
{
if (d[v.v] > d[x.v] + v.dis)
{
d[v.v] = d[x.v] + v.dis;
Q.push((Node){d[v.v], v.v});
}
}
}
}
void add_edg(int u, int v, int w)
{
edg[u * 51].push_back({0, v * 51 + w});
for (int i = 1; i <= 50; i++)
{
edg[u * 51 + i].push_back({(i + w) * (i + w), v * 51});
}
}
int main()
{
io();
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
add_edg(u - 1, v - 1, w);
add_edg(v - 1, u - 1, w);
}
for (int i = 0; i < MAXN; i++)
{
d[i] = inf;
}
Dijkstra(d, 0);
for (int i = 1; i <= n; i++)
{
if (d[(i - 1) * 51] != inf)
cout << d[(i - 1) * 51] << " ";
else
{
cout << "-1 ";
}
}
// cin >> n;
}
1486 D
题意
给一个长度为n的数组,和一个数k,问子区间大于等于k的区间中,最大的中位数
题解
二分法答案,判断mid的方法是把小于mid的置为-1,大于的置为1,做一个前缀和sum, 那么对于每一个sum[i],在[1,i-k]中找最小的sum,相减判断是否大于一,如果大于一就是可行的
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 2e5 + 10;
const int inf = 1e9 + 7;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
int n, k;
int sum[MAXN];
int mn[MAXN];
int a[MAXN];
bool ju(int x)
{
// cout << "x = " << x << "\n";
mn[0] = 0;
for (int i = 1; i <= n; i++)
{
sum[i] = sum[i - 1] + (x > a[i] ? -1 : 1);
mn[i] = min(sum[i], mn[i - 1]);
}
// mn[0] = 0;
for (int i = k; i <= n; i++)
{
if (sum[i] - mn[i - k] > 0)
return true;
}
return false;
}
int main()
{
io();
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
int le = 1, ri = n;
int ans = 1;
while (le <= ri)
{
int mid = (le + ri) / 2;
if (ju(mid))
{
ans = mid;
le = mid + 1;
}
else
{
ri = mid - 1;
}
}
cout << ans << "\n";
// cin >> n;
}
1490 G
题解
维护一个递增的前缀和 b[ ],并记录达到该前缀和的位置 id[ ]。
(1)判断是否存在,若x > 前缀和的最大值且整个数组的和 <= 0,则不存在。
(2)如果第一轮就可以找到 >= x 的数,直接lower_bound;如果需要 k 轮,并且最后加的一个数为b[i],即求一对 i,k 满足 k * sum + b[i] >= x,且使 id[i] - 1 + k * n最小,显然应该先让k取最小,就是用最大的 b[i] 来求最小的 k
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mx[200050];
void solve()
{
int n, m;
cin >> n >> m;
ll s = 0;
for (int i = 1; i <= n; i++)
{
ll d;
cin >> d;
s += d;
mx[i] = max(mx[i - 1], s);
}
while (m--)
{
ll q;
cin >> q;
if (mx[n] >= q)
cout << (lower_bound(mx + 1, mx + 1 + n, q) - mx) - 1 << ' ';
else
{
if (s <= 0)
cout << "-1 ";
else
{
ll d = q - mx[n];
ll tim = (d + s - 1) / s;
q -= tim * s;
cout << (lower_bound(mx + 1, mx + 1 + n, q) - mx + tim * n) - 1 << ' ';
}
}
}
cout << '\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
1487 E 2021.2.22
题意
https://codeforces.com/contest/1487/problem/E
题解
很容易理解的题意,也很容易想到,每次在前一个菜品中寻找可以跟当前菜品对应的价值最小的菜品,这可以说是dp也可以说是贪心,dp[i][j]代表第i种菜的第j个一定要选的时候,最小价值,从前往后dp即可,对于可能出现的不能匹配的值,用multiset维护
code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 2e5 + 10;
inline void io()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
}
int n[5];
LL p[5][MAXN];
vector<int> bad[5][MAXN];
LL dp[5][MAXN];
const LL inf = 1e15;
int main()
{
for (int i = 1; i <= 4; i++)
{
cin >> n[i];
}
for (int i = 1; i <= 4; i++)
{
for (int j = 1; j <= n[i]; j++)
{
cin >> p[i][j];
}
}
for (int i = 1; i <= n[1]; i++)
{
dp[1][i] = p[1][i];
}
for (int i = 2; i <= 4; i++)
{
int m;
cin >> m;
for (int j = 1; j <= m; j++)
{
int x, y;
cin >> x >> y;
bad[i][y].emplace_back(x);
}
}
for (int i = 2; i <= 4; i++)
{
multiset<LL> st;
for (int j = 1; j <= n[i - 1]; j++)
{
st.insert(dp[i - 1][j]);
}
for (int j = 1; j <= n[i]; j++)
{
for (auto k : bad[i][j])
{
st.erase(st.find(dp[i - 1][k]));
}
if (st.empty())
{
dp[i][j] = inf;
}
else
{
dp[i][j] = *st.begin() + p[i][j];
}
for (auto k : bad[i][j])
{
st.insert(dp[i - 1][k]);
}
}
}
LL ans = inf;
for (int i = 1; i <= n[4]; i++)
{
ans = min(ans, dp[4][i]);
}
if (ans == inf)
{
cout << "-1\n";
}
else
{
/* code */
cout << ans << '\n';
}
// cin >> n[1];
}

被折叠的 条评论
为什么被折叠?



