好题

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][mk]

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(n1)2cnt1(cnt11)cnt1(ncnt1)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]i1i1j使k0
长为k的连续0,贡献为 x ( x − 1 ) 2 \frac{x(x - 1)}{2} 2x(x1)

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(bk1,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]=maxxai(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]=maxxairposx<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=ab的个数

题解

数位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][jb[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];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值