2020牛客暑期多校训练营(第五场)

A Portal

f [ i ] [ p ] f[i][p] f[i][p]表示当前已经完成了前i个任务,当前正在 a [ i ] a[i] a[i]上,传送门的位置在 p p p
•可以证明,只需要3种转移,就可以覆盖所有情况:
•1.直接从a[i]走到a[i+1]
•2.枚举走到a[i+1]之后,传送门的位置变为了哪个节点,设这个节点是q。第二种转移是从a[i]走到 q,在q设置传送门,从q传送到 p p p,再从 p p p走到 a [ i + 1 ] a[i + 1] a[i+1]
•3.第三种转移是从 a [ i ] a[i] a[i]传送到 p p p,从 p p p走到 q q q,在 q q q设置传送门,最后从 q q q走到 a [ i + 1 ] a[i + 1] a[i+1]
•复杂度为 O ( k n 2 ) O(kn^2) O(kn2)

初始状态的最短路用Floyd最短路求出。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int MAXN = 305;
const LL INF = 2e18;
inline void io()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
}
LL f[MAXN][MAXN];
LL d[MAXN][MAXN];
int a[MAXN * 2];
int main()
{
    int n, m, k;
    scanf("%d %d %d", &n, &m, &k);    
    memset(d, 0x16, sizeof(d));
    memset(f, 0x16, sizeof(f));
    // cout << "d = " << d[1][2] << '\n';
    for (int i = 1; i <= n; i++)
        d[i][i] = 0;
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        LL w;
        scanf("%d %d %lld", &u, &v, &w);
        d[u][v] = d[v][u] = min(d[u][v], w);
    }

    for (int k = 1; k <= n; k++)
    {
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
            {
                // if (i == j || k == i || k == j)
                //     continue;
                d[i][j] = d[j][i] = min(d[i][j], d[i][k] + d[k][j]);
            }
    }
    // for (int i = 1; i <= n; i++)
    // {
    //     for (int j = 1; j <= n; j++)
    //     {
    //         if (d[i][j] != INF)
    //             cout << "i = " << i << " j = " << j << " dpij = " << d[i][j] << "\n";
    //     }
    // }
    for (int i = 1; i <= n; i++)
        f[1][i] = d[1][i];
    a[1] = 1;
    for (int i = 1; i <= k; i++)
    {
        scanf("%d %d", &a[i * 2], &a[i * 2 + 1]);
    }
    for (int i = 2; i <= 2 * k + 1; i++)
    {
        for (int p = 1; p <= n; p++)
        {
            f[i][p] = min(f[i][p], f[i - 1][p] + d[a[i - 1]][a[i]]);
            for (int q = 1; q <= n; q++)
            {
                f[i][q] = min(f[i][q], f[i - 1][p] + d[p][q] + d[q][a[i]]);
                f[i][q] = min(f[i][q], f[i - 1][p] + d[a[i - 1]][q] + d[p][a[i]]);
            }
        }
    }
    LL ans = f[2 * k + 1][1];
    for (int i = 1; i <= n; i++)
    {
        ans = min(ans, f[2 * k + 1][i]);
    }
    printf("%lld", ans);
    // int t;
    // cin >> t;
}

H Interval

本题需要线段树和主席树一起来求解,对于区间的与操作,容易想到采用线段树来求解。
容易想到,对于每一个 A i A_i Ai作为右端点,向左的所有区间里,不同的值的个数为 x x x(其中, x = A i x = A_i x=Ai的二进制中,1的个数)。
主席树的每一个根节点的 r t [ i ] rt[i] rt[i],代表以序列中的第 i i i个点为右端点,向左的每一个区间里得不同得数的个数。
每加入一个 A i A_i Ai,就在主席树中新建一个根节点,将前一个出现值为 A i A_i Ai的位置减一,当前位置加一,枚举固定该节点为右端点,向左的每一次减少的位置,在那个位置加一,前一个这个值的位置减一。
这样在询问时,只要在R为根节点的主席树中询问一的个数的和,即为不同的个数的和

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int MAXN = 1e5 + 5;
inline void io()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
}
namespace Chairman
{
    const int N = 1e5 + 5;
    int ls[N * 300], rs[N * 300], rt[N], sum[N * 300], tot;
    void update(int l, int r, int root, int last, int p, int v)
    {
        ls[root] = ls[last];
        rs[root] = rs[last];
        sum[root] = sum[last] + v;
        if (l == r)
            return;
        int mid = l + r >> 1;
        if (mid >= p)
            update(l, mid, ls[root] = ++tot, ls[last], p, v);
        else
            update(mid + 1, r, rs[root] = ++tot, rs[last], p, v);
    }
    int query(int l, int r, int root, int ql, int qr)
    {
        if (l >= ql && r <= qr)
            return sum[root];
        int mid = l + r >> 1;
        int ans = 0;
        if (mid >= ql)
            ans = query(l, mid, ls[root], ql, qr);
        if (mid < qr)
            ans += query(mid + 1, r, rs[root], ql, qr);
        return ans;
    }
}; // namespace Chairman
namespace Segtree
{
    const int MAXN = 1e5 + 5;
    struct node
    {
        int l, r;
        LL sum, lazy;
    } tree[MAXN << 2];
    LL a[MAXN]; //you can put the num to this first
    inline 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].sum = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(i * 2, l, mid);
        build(i * 2 + 1, mid + 1, r);
        tree[i].sum = tree[i * 2].sum & tree[i * 2 + 1].sum;
    }
    inline LL search(int i, int l, int r)
    {
        if (tree[i].l >= l && tree[i].r <= r)
        {
            return tree[i].sum;
        }
        if (tree[i].r < l || tree[i].l > r)
            return 0;
        LL ans = (1 << 30) - 1;
        if (tree[i * 2].r >= l)
            ans &= search(i * 2, l, r);
        if (tree[i * 2 + 1].l <= r)
            ans &= search(i * 2 + 1, l, r);
        return ans;
    }
} // namespace Segtree
int a[MAXN];
map<int, int> pre;
int main()
{

    // Chairman::tot = 0;
    // Chairman::rt[0] = 0;
    // Chairman::update(1, 10, Chairman::rt[1] = ++Chairman::tot, Chairman::rt[0], 3, 1);
    // Chairman::update(1, 10, Chairman::rt[2] = ++Chairman::tot, Chairman::rt[1], 1, -1);
    // Chairman::update(1, 10, Chairman::rt[3] = ++Chairman::tot, Chairman::rt[2], 6, 2);
    // cout << Chairman::query(1, 10, Chairman::rt[1],1,3) << '\n';
    // cout << Chairman::query(1, 10, Chairman::rt[3],3,6) << '\n';
    // cout << Chairman::query(1, 10, Chairman::rt[3],1,6) << '\n';

    int n;
    // cin >> n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        // cin >> a[i];
        scanf("%d", &a[i]);
        Segtree::a[i] = a[i];
    }
    Segtree::build(1, 1, n);
    Chairman::tot = 0;
    Chairman::rt[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        if (pre.find(a[i]) != pre.end())
        {
            // int nowrt = Chairman::tot;
            Chairman::update(1, n, Chairman::rt[i] = ++Chairman::tot, Chairman::rt[i - 1], pre[a[i]], -1);
            int now = ++Chairman::tot;
            Chairman::update(1, n, now, Chairman::rt[i], i, 1);
            Chairman::rt[i] = now;
        }
        else
            Chairman::update(1, n, Chairman::rt[i] = ++Chairman::tot, Chairman::rt[i - 1], i, 1);
        pre[a[i]] = i;
        int v = a[i], p = i;
        for (int j = 30; j; j--)
        {
            int l = 1, r = p - 1;
            int ans = 0;
            while (l <= r)
            {
                int mid = (l + r) >> 1;
                if (Segtree::search(1, mid, i) < v)
                    l = mid + 1, ans = mid;
                else
                {
                    r = mid - 1;
                }
            }
            if (ans == 0)
                break;
            v = Segtree::search(1, ans, i);
            p = ans;
            int now;
            if (pre.find(v) != pre.end())
            {
                now = ++Chairman::tot;
                Chairman::update(1, n, now, Chairman::rt[i], pre[v], -1);
                Chairman::rt[i] = now;
            }
            now = ++Chairman::tot;
            Chairman::update(1, n, now, Chairman::rt[i], p, 1);
            Chairman::rt[i] = now;
            pre[v] = p;
        }
    }
    int Q;
    scanf("%d", &Q);
    int lastans = 0;
    while (Q--)
    {
        int L, R;
        scanf("%d %d", &L, &R);
        L = (L ^ lastans) % n + 1;
        R = (R ^ lastans) % n + 1;
        if (L > R)
            swap(L, R);
        lastans = Chairman::query(1, n, Chairman::rt[R], L, R);
        printf("%d\n", lastans);
    }
    // int t;
    // cin >> t;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值