安装Bazel并编译运行

- 1 min read

关于远程服务器

本文使用的操作系统是ubuntu 22.04。

如果需要运行trpc-cpp,服务器的内存要>2gb,否则后期编译运行时可能会有问题。

另外,直接登入root操作可能会遇到奇怪的问题。

github加速

参考这个github加速 - 妖岭 - 博客园 (cnblogs.com),后续下载会快一点。

bazel安装配置

Bazel官网中可以找到推荐的安装方式是使用Bazelisk,一个有版本管理功能的Bazel启动器。 Pasted image 20241016225027.png

可以直接下载deb文件并安装

wget https://github.com/bazelbuild/bazelisk/releases/download/v1.22.0/bazelisk-amd64.deb

dpkg -i bazelisk-amd64.deb

理论上,只要输入bazel,bazelisk就会初次启动,自动下载Bazel某个版本。那么只要输入bazel --version后能看到版本信息,就能确定Bazel安装成功。

实际上,可能会在卡顿许久后出现以下提示信息: image.png

Bazel版本设置

bazelisk的github仓库里有写到它在初次启动时如何确定下载哪个Bazel版本。 image.png 可以看到,如果没有预先设置要使用的版本,它就会自动搜索使用最新的版本信息。但它搜索的这个链接在不翻墙的情况下无法访问,因此会出现如上所示的报错。

解决方法就是,预先设置要使用的version来避开这一步,可以利用.bazeliskrc文件。 image.png 用这个指令即可指定version:echo "USE_BAZEL_VERSION=6.1.0" > .bazeliskrc6.1.0应被实际选择版本号替代。 至于版本的选择,如果需要编译trpc-cpp,事实上需要Bazel版本是>=3.5.1,<7的。(参考这个这个)

可以在上面的googleapi链接或华为云 bazel找到所有版本号。

其他参考链接

Bazel 教程:构建 C++ 项目

另一种安装Bazelisk方式

Steps for installing Bazelisk are not new-developer friendly

编译运行trpc-cpp

只要按照trpc-cpp/docs/zh/setup_env.md上的说明操作即可,具体如下:

# 进入根目录trpc-cpp
cd trpc-cpp

# 用bazel编译并运行框架提供的example示例
./run_examples.sh

另外,如果先编译helloworld demo来测试会快一点。 ./examples/helloworld/run.sh

CCPC Harbin 2023

- 2 mins read

一次很乐的vp,简单补下题

J. Game on a Forest

Problem - J - Codeforces

题意是有一个森林,2个人交替取走 一个节点及其所连所有边 或 一条边,无法操作者输。问先手第一步有多少操作能导向胜利局面。

边数、点数$O(1e5)$

森林是由一些分散的树构成的,每个树的情况都有独立性,组合在一起更显复杂。但经典的公平组合游戏 - OI wiki中对这种情况早有叙述。毕竟在Nim游戏中,也有几堆石头,每堆石头的情况是相对独立的。

截图OI wiki上的SG定理 image.png 那么,只要将一棵树建模为有向图,分析它的SG函数即可。 较为复杂的事是,玩家有2种操作:拿边和拿点. 易得1个点的树SG值为1,2个点的树SG值为2(#-# -> # # 或 # , SG分别为0,1). 3个点的树形态也唯一,可以继续推得是1,进而推得4是2. 开始有规律了,似乎是奇数节点数的树SG值为1,偶数的为2.

在这里先泛泛地想一下,对多个点的树进行操作可能的所有情况:

  • 拿走1个点,可能
    • 这棵树的节点会少一个(拿走的没儿子)
    • 分为多棵树(拿走的有儿子)
  • 拿走1边,会
    • 分为2棵树

那么对那个从奇偶角度提出的假设,用数学归纳的思路,试证明:

  • 拿走1个点,可能
    • 这棵树的节点会少一个(拿走的没儿子)
      • 奇树 变为偶树 (SG为2)
      • 偶树 变为奇树 (SG为1)
    • 分为多棵树(拿走的有儿子)
      • 奇树可能分为:
        • 偶数个奇树 奇数个偶树 (SG为2)
        • 偶数个奇树 偶数个偶树 (SG为0)
      • 偶树可能分为:
        • 奇数个奇树 奇数个偶树 (SG为3)
        • 奇数个奇树 偶数个偶树 (SG为1)
  • 拿走1边,会
    • 分为2棵树
      • 奇树分为
        • 1奇1偶 (SG为3)
      • 偶树可能分为
        • 2偶 (SG为0)
        • 2奇 (SG为0)

发现是对的.

方格图

- 5 mins read

引子

由n*m的方格组成的二维平面,形成了一类常见而有趣味的问题。

方格可以蕴含着一个图或一种用于遍历的次序关系,可能作为思考的前提,也可能仅仅是可被忽略的问题背景。

2024icpc湖北省赛H

n*m的方格上有k(<10)个含🐟方格,1个方格最多含3条鱼,可以任选一个方格扔炸弹,炸出邻近方格的鱼,问最少炸几下能把🐟炸完。

在这里,问题的正解是DP,定义状态特征为每个方格还剩的鱼数,用状压表示,状态数是 $O(4^10=1e6)$的。记录每个状态所需最小炸鱼数,转移时枚举所有可能的方格($O(50)$)。

方格图的可能特征

有很多不同类别与角度,如

  • 方格属于
    • 行、列
    • 对角线
    • 九宫格 这是一类最基础的常识性的表示方式。对一个格子(x,y),可以获知它的行为x,列为y,对角线表示为x+y(2-n+m), x-y(1-m - n-1)。若平面被划分为九宫格之类的结构,它所在的宫格坐标可记为((x-1)/k1,(y-1)/k2) (n/k1-1, m/k2-1)。

不同格子的关系

cf div4 F

200组测试,时限4s。 给出一组7*7方格,每个方格被涂了W,B两种颜色。可以选一些涂‘B’的格子涂成‘W’,问最少选几个修改,能使方格中不出现5个‘B’组成的‘X’? 发现可以把方格分成两组(25+24),只有每组内部可能组成‘X’形。 发现每组最多选4个即可消除所有的X。即搜索复杂度达到(C(25,4)+C(24,4))*50左右 image.png

坐标

最普通的坐标就是(行数,列数),注意从0还是1开始,x到底对应行还是列。 一个思维trick是坐标变换。

在坐标的意义下,方便考虑距离的概念。

距离

欧式距离
曼哈顿距离

image.png

在国际象棋棋盘上,车从一个格子走到另一个格子的最短距离就是曼哈顿距离。 若网格图上的一个点只能到上下左右 4 个点,且到这 4 个点的距离都相同,则该网格图上两点的距离也为曼哈顿距离。

例:求点对曼哈顿距离最大值

二维平面上,共有n(<=5e4)个点,给出坐标,求曼哈顿距离最大的点对的最大值。 |x1-x2|+|y1-y2| = +-(x1-x2+y1-y2)或 = +-(x1-x2+y2-y1) 故只需分别求求所有点的(x+y)、(x-y) 最大、最小值,相减取最大值即可

切比雪夫距离

image.png 在国际象棋棋盘上,国王从一个格子走到另一个格子的最短距离是切比雪夫距离。(从8个方向走) 若网格图上的一个点只能到周围 8 个点,且到这 8 个点的距离都相同,则该网格图上两点的距离也为切比雪夫距离。

例:abc351_e

有n(2e5)个点,定义点的距离是:从(x,y)格一次可以移动到 (𝑥+1,𝑦+1),(𝑥+1,𝑦−1), (𝑥−1,𝑦+1), (𝑥−1,𝑦−1),从A到B格的最少移动次数就是A到B的距离;若移不到就是0. 现在,求所有点的距离之和。

这里的距离定义和切比雪夫距离很像,但忽略了上下左右平移的情况。 得到如果横纵坐标之和奇偶性相同,距离大小和切比雪夫距离一致(可以列方程得到),否则就到不了,距离为0。

因此先分成两类讨论,再对每一类内部求所有点对切比雪夫距离和。

曼哈顿距离与切比雪夫距离互转

圆是由从圆心向各个固定XXX距离标示出来的点围成的区域。 曼哈顿距离和切比雪夫距离中的圆都是正方形 半径为r时,曼哈顿距离的圆边长√2r,切比雪夫距离的圆边长为2r,因此二维切比雪夫距离可视为等同于旋转且放大过的二维曼哈顿距离。 然而这种介于L1与L∞的相等关系不能延伸到更高的维度

2023ICPC沈阳 M题 Outro: True Love Waits

- 3 mins read

题意

有一张无限大的图,每个整数都对应图上一个点。对两个整数,如果二进制仅有第i位不同,则它们有一条权重为i的边相连。 在图上移动时,规则是从目前所在点,走过权重最小的边到达下一个点,然后把这条边删掉。 输入s,t (二进制表示,长度<=1e6)和k,问若以s为起点,可否第k次到达t点?那时走了几步? 对1e9+7取模。

分析及实现

手推规律后,发现有循环性。如下图:

代码

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define endl '\n'
typedef unsigned long long ull;

const int N = 1e5;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;

int qpow(int x, int k)
{
    int ret = 1;
    x %= MOD;
    while (k)
    {
        if (k & 1)
        {
            ret *= x;
            ret %= MOD;
        }
        x *= x;
        x %= MOD;
        k >>= 1;
    }
    return ret;
}

int inv(int x, int mod)
{
    return qpow(x, mod - 2);
}

map<int, int> mp;

void solve()
{
    string s, t;
    int k;
    cin >> s >> t >> k;
    if (t == s) /////
    {
        int INV = inv(3, MOD);
        int b1 = (1 + INV) % MOD;
        int ans = b1 * qpow(4, k - 1) % MOD;
        ans -= 1;
        ans += MOD;
        ans %= MOD;
        ans -= INV;
        ans += MOD;
        ans %= MOD;
        cout << ans << endl;
        return;
    }

    if (s.length() < t.length())
    {
        swap(s, t);
    }
    int n = s.length();
    int m = t.length();

    for (int i = m - 1; i >= 0; --i)
    {
        int j = i - m + n;
        s[j] = '0' + (s[j] - '0') ^ (t[i] - '0');
    }
    // s
    if (s.length() % 2 != 0)
    {
        s = "0" + s;
    } // 0123
   // cout << "s " << s << endl;
    int fir = 0;
    int base = 1;
    for (int i = s.length() - 1; i > 0; i -= 2)
    {
        int num = (s[i - 1] - '0') * 2 + s[i] - '0';
        if (i == s.length() - 1)
        {
            fir += (mp[num]) * base % MOD;
        }
        else
            fir += (mp[num] - 1) * base % MOD;
        fir %= MOD;
        base = (base * 4 + 1) % MOD;
    }
    fir -= 1;
    fir += MOD;
    fir %= MOD;
  //  cout << "fir " << fir << endl;

    int cnt00 = 0;
    for (int i = s.length() - 1; i > 0; i-=2)
    {
        if (s[i] == '0' && s[i - 1] == '0')
        {
            cnt00++;
        }
        if(s[i]=='1'||s[i-1]=='1')
        {
            break;
        }
    }
    if (1 + cnt00 < k)
    {
        cout << -1 << endl;
        return;
    }

    int INV = inv(3, MOD);
    int b1 = (1 + INV) % MOD;
    int ans = b1 * qpow(4, k - 1) % MOD; // add
    ans -= 1;
    ans += MOD;
    ans %= MOD;
    ans -= INV;
    ans += MOD;
    ans %= MOD;
    ans += fir;
    ans %= MOD;

    cout << ans << endl;
}

signed main()
{
    // cout.flags(ios::fixed); cout.precision(8);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    mp[0] = 1;
    mp[1] = 2;
    mp[3] = 3;
    mp[2] = 4;

    int T = 1;
    cin >> T;
    for (int i = 1; i <= T; ++i)
    {
        solve();
    }
    return 0;
}
/*

*/

回顾

2023ICPC沈阳 I题 Three Rectangles

- 5 mins read

题意

有一个(0,0)(左下角)到(H,W)(右上角)的矩形区域,给出3个小矩形的h和w,要求3个矩形盖住矩形区域的放置方案:要求3个矩形不能旋转,只能放到整点上,不能超出矩形区域,可以重叠。mod 1e9+7。 H,W范围1e9, $1\leq h_i \leq H, 1 \leq w_i \leq W$

分析及实现

由3个小矩形盖住大矩形,通过思考可以发现,至少有一个小矩形有一条边等于大矩形的边长。 (h=H or w=W)

暂时以h = H 为例分析:

如果有至少一个矩形满足$h_i=H,w_i=W$,这一个位置固定,其它的可以随意放。

如果只有一个矩形x满足h_x = H,则x一定放于大矩形一侧,其它两个一定盖住另外的两角,否则只能x盖满整个区域(上面已讨论到)。(至于一个放于一角时已经能和一侧的盖满整个大矩形的情况,对应有两个以上矩形满足h = H) 此时先判断另外两个和这个能不能盖满(则另外两个的宽和这个合起来能不能盖满W,另外两个的高度相加能不能盖满H)。 若能盖满,答案为4: 考虑到另外两个矩形的上下位置和x的左右位置,盖法要*4。

如果恰有两个矩形x,y满足h_x=H,h_y=H,则将这两个放于两侧,则这两个可以盖满大矩形(即宽度相加盖满W)等价于三个可以盖满。若可以,则计算第三个随意摆放的方案数即可。先不考虑左右矩形的位置,最后要乘以2。

如果三个x,y,z都满足h=H,则同样有两个矩形放于两侧。这里我的想法是模拟相当于1*W的长条形上三个滑动的情况,枚举放在两端和中间的矩形(限定了左右矩阵分别是哪个后,共有三种可能),然后计算中间的矩形能够滑动的距离(1.要盖住中间的空白区域;2.不能超过边界)(可见如果两端能盖满,中间的又可以随意摆放了)。 但这里可能会算重,因为中间随意摆放的那个矩形若能放到边缘,则会和其它情况算重:1、2在左边,3在右边若可行,则会被计数两次,这种情况会发生等价于1和2中至少一个能和3盖满。 因此,只要对每个矩形判断能不能和任意一个其它的矩形盖满大矩形,若能则方案数-1。 最后同样*2。

ps:一开始我不太确定,所以在小数据范围内循环模拟计数,对照了一下,认为是对的。

以h = H 为例的分析结束后:

如果h=H和w=W都不发生,方案数为0。 如果都发生,只需考虑不同矩形分别满足两项条件的情况: 可能满足h=H和w=W的分别有1个,此时它们一定分别盖住一边,选择一种讨论即可。 可能满足条件的分别有1个和2个,比如满足h=H和w=W的分别有1、2个,此时应该从w=W出发讨论。 因为此时满足h=H的不一定盖住矩形一边。

代码

因为开始思路不清,写的有点麻烦

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define endl '\n'
typedef unsigned long long ull;

const int N = 1e5;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int MOD = 1e9 + 7;

void solve()
{
    int H, W;
    cin >> H >> W;
    int h[4], w[4];
    int cf = 0;
    int ch = 0, cw = 0;
    for (int i = 1; i <= 3; ++i)
    {
        cin >> h[i] >> w[i];
        if (h[i] == H && w[i] == W)
        {
            cf = 1;
        }
        if (h[i] == H)
        {
            ch++;
        }
        if (w[i] == W)
        {
            cw++;
        }
    }
    // cout << ch << " " << cw << endl;

    int ans = 1;

    if (cf == 1)
    {
        int ans = 1;
        for (int i = 1; i <= 3; ++i)
        {
            ans *= (H - h[i] + 1) % MOD * (W - w[i] + 1) % MOD;
            ans %= MOD;
        }
        cout << ans << endl;
        return;
    }

    int check = max(ch, cw);

    if (check == 0)
    {
        cout << 0 << endl;
        return;
    }

    if (ch < cw) // 对应 1 vs 2 的情况 ,旋转矩形
    {
        swap(H, W);
        swap(ch, cw);
        swap(h, w);
    }

    // ch!=0

    if (ch == 3)
    {
        ans = 0;
        if (w[1] + w[2] + w[3] < W)
        {
            cout << 0 << endl;
            return;
        }
        for (int mid = 1; mid <= 3; ++mid)
        {
            int cover = w[1] + w[2] + w[3] - w[mid];
            int add1 = 0;
            if (cover >= W)
            {
                add1 += (W - w[mid] + 1) % MOD;
                add1 %= MOD;
            }
            else
            {
                int step = W - cover;
                // 1    4   6
                int l, r;
                if (mid == 1)
                    l = 2, r = 3;
                if (mid == 2)
                    l = 1, r = 3;
                if (mid == 3)
                    l = 1, r = 2; //强制安排两侧左右位置
                int ndl2 = w[l] + 1;
                int ndl1 = W - w[r] - w[mid] + 1;
                ndl1 = max(ndl1, (int)1);
                ndl2 = min(ndl2, W - w[mid] + 1);
                if (ndl2 - ndl1 + 1 > 0)
                    add1 += ndl2 - ndl1 + 1;
                add1 %= MOD;
            }
            ans += add1;
            ans %= MOD;
        }

        for (int r = 1; r <= 3; ++r)
        {
            int l = 0;
            for (int i = 1; i <= 3; ++i)
            {
                if (r == i)
                    continue;
                l = max(w[i], l);
            }
            if (l + w[r] >= W)
            {
                ans -= 1; ///
                ans %= MOD;
                ans += MOD;
                ans %= MOD;
            }
        }

        ans *= 2;
        ans %= MOD;
        cout << ans << endl;
        return;
    }

    int L = 0;
    int pos = -1;
    for (int i = 1; i <= 3; ++i)
    {
        if (h[i] == H)
        {
            L = w[i];
            pos = i;
            break;
        }
    }

    if (h[1] + h[2] + h[3] - h[pos] < H)
    {
        cout << 0 << endl;
        return;
    }

    if (ch == 2)
    {
        for (int i = 1; i <= 3; ++i)
        {
            if (h[i] == H && i != pos)
            {
                if (w[i] + L < W)
                {
                    cout << 0 << endl;
                    return;
                }
            }
        }
        for (int i = 1; i <= 3; ++i)
        {
            if (h[i] != H)
            {
                ans *= (H - h[i] + 1) % MOD * (W - w[i] + 1) % MOD;
                ans %= MOD;
            }
        }
    }
    else
    {
        for (int i = 1; i <= 3; ++i)
        {
            if (i != pos)
            {
                if (w[i] + L < W)
                {
                    cout << 0 << endl;
                    return; ///////
                }
            }
        }
        ans *= 2;
        ans %= MOD;
    }
    ans *= 2;
    ans %= MOD;

    cout << ans << endl;
}

signed main()
{
    // cout.flags(ios::fixed); cout.precision(8);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T = 1;
    cin >> T;
    for (int i = 1; i <= T; ++i)
    {
        solve();
    }
    //////////////////////////用来测试的
    // int ww[3] = {5, 5, 4};
    // int n = 5;
    // int ans = 0;
    // for (int i = 1; i <= n; ++i)
    // {
    //     for (int j = 1; j <= n; ++j)
    //     {
    //         for (int k = 1; k <= n; ++k)
    //         {
    //             vector<int> s(n + 1, 0);
    //             if (i + ww[0] - 1 > n || j + ww[1] - 1 > n || k + ww[2] - 1 > n)
    //                 continue;
    //             for (int l = i; l <= i + ww[0] - 1; ++l)
    //             {
    //                 s[l] = 1;
    //             }
    //             for (int l = j; l <= j + ww[1] - 1; ++l)
    //             {
    //                 s[l] = 1;
    //             }
    //             for (int l = k; l <= k + ww[2] - 1; ++l)
    //             {
    //                 s[l] = 1;
    //             }
    //             int ok = 1;
    //             for (int l = 1; l <= n; ++l)
    //             {
    //                 if (s[l] == 0)
    //                     ok = 0;
    //             }
    //             if (ok)
    //             {
    //                 ans++;
    //             }
    //         }
    //     }
    // }
    // cout << ans << endl;

    return 0;
}


  	 	 			 	 		    	 	  	 	 		

回顾

其实不难,细致即可。

DP-PPT

- 2 mins read

简单DP

writingdog


目录

  • 什么是DP
  • DP怎么写
  • 例题

DP的简单描述

DP是适用于一类问题的解题策略。 问题往往需要输出一个东西的存在性、次数或者最大/最小值,但不需要给出具体的方案。

一种 或许可行 的思路是:给问题划分范围,解决这个问题的较小范围版本,由此算出这个问题的答案。(前i个 -> 前i+1个)【子问题和原问题】

能够联想到递推和递归:可以先写出初态,然后用for循环不断推,直到推出答案。也可以不断从大问题递归到小问题,直到到达可确定的初态。 【DP的两种写法】


状态

如何从小范围问题推向大范围问题? 需要存下来小范围问题结果的某些信息,以此计算得到大范围的相同类型的信息。 这些信息(称为状态)应该能够刻画问题的特征 【DP的状态设计】 应该存在确定的计算方式,能从小范围问题的状态算出大范围问题的状态 【DP的转移方程】

如何高效计算? 只存储、处理我们需要的(最优)信息,减小复杂度。

状态大多用数组存储,也存在更复杂的方式,比如 F - Fighter Takahashi (atcoder.jp) F-来点每日一题_2024牛客寒假算法基础集训营4 (nowcoder.com)


一个例子

给一个 $n\times m(2 \leq n,m \leq 10^2)$ 的网格,一开始在左上角 $(1,1)$ ,每一步只能向右或向下走,不能经过 ‘#’ 格子,求走到右下角 $(n,m)$ 有多少种走法。

原问题:到(i,j)有几种走法 子问题:到(i-1,j),到(i,j-1)有几种走法

dp[i][j]表示到$(i,j)$的走法数,答案是dp[n][m]。 可得转移方程dp[i][j]=dp[i-1][j]+dp[i][j-1]。 (二重for循环,i从小到大,j从小到大) 对 ‘#’ 格子,将对应的dp[i][j]赋为0


问题特征

能用动态规划解决的问题有以下几个特征。

  • 子问题重叠 子问题是原大问题的小版本,解决方法完全一样,但范围、规模更小。计算大问题时,需要知道子问题的解。 把大问题拆成小问题

如果有大量的重叠子问题,我们可以用空间将这些子问题的解存储下来,避免重复求解相同的子问题,从而提升效率。

  • 最优子结构 大问题的最优解包含小问题的最优解,可以从小问题的最优解推出大问题的最优解

  • 无后效性 已经求解的子问题,不会再受到后续决策的影响。 只需要考虑子问题的状态,不需要知道经过什么步骤到达这个状态。


基本思路

动态规划分为几步:

  1. 将原问题划分为若干子问题,提取这些子问题的特征(称之为 状态),用状态描述问题
  2. 寻找每一个状态的可能 决策,写 状态转移方程
  3. 写程序实现

转移写法

  • for循环(递推)
for(int i=1;i<=n;++i)
{
	for(int j=1;j<=m;++j)
		dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]);
}

填表法 :当前点的状态,根据状态方程,根据之前点的状态推导出来。 刷表法:由当前点的状态,更新其他点的状态。

从dijkstra开始

- 1 min read

开学不久补了一道题目,https://atcoder.jp/contests/abc319/tasks/abc319_f

基础性的步骤是用优先队列维护可以走到的点,弹出代价最小的点,实现一个动态的贪心过程。相当于我站在一棵树上的一点,可以移动到一些与这棵树相接的点,从而扩大树的范围,也扩大自己将来可以移动的范围。当时觉得这个priority_queue的使用虽然基础,但想不到.

印象里暑假里研究过dijkstra算法,做过几道题. 看到过多源bfs等等,但最后也没有留下什么印象。

上学期在atcoder上看到一道题,是dijkstra的板子改了改,题意是有k种不同面值的钞票,求能凑出第m小的面值。解法是建k条不同的边,从0面值开始搜索,放进优先队列,求第m个出队的面值。https://atcoder.jp/contests/abc297/tasks/abc297_e

我觉得这道题可以被重述成一道图论的题。那么重述前后不变的本质是什么呢?

事实上在这里priority_queue做到的也是储存所有可能取到的值,从中拿出最小的第k个。无非这里我们一边取一遍算。这个用法本质上很简单。

印象里当年百度之星初赛第二场也有一道可以被建模为图论的题。

然后,昨天补到一道题,这道题比较dijkstra了。https://codeforces.com/gym/104460/problem/K

题意是$n$点$m$边无向图,Baobao从点$1$出发,想走到k个点之中的一个,任何一个都可以,边权是所需的时间。但当他走到第$i$点时,有$d_{i}$条以$i$点为端点的边会堵住(哪$d_{i}$条都有可能,每次走到$i$点时,堵住的边也可能不一样),问在最差情况下,Baobao走完的最小时间是多少?

如果从起点开始顺着走的话,不能确定哪几条边堵着最差;把k个终点的dis设为0,放进优先队列,然后倒推跑dijkstra,这时和之前的几道题情况一样,当点i弹出优先队列时,我们拿到一个点i到终点的最小距离。这时让从点i连在这条路径的边被堵住,即,每次Baobao走到点i,这条边总是堵的。否则只要这条边不堵,就可以最快的到达某个终点。

所以此时的点i也不能再转移出别的点了。当点i第二次被弹出时,找到了到它的第二短路径,以此类推。那么,当第$d_{i}+1$次弹出i点时,找到的就是最坏情况下,点i到终点的最短路。

Luogu P3292

- 1 min read

背景

学长讲线性基时挂了几道题,这道题原本写不出来,看了题解后瞎写了一下,又对着原代码调了调,竟然过了。 image.png

打算算一下时间复杂度,分析解法和代码的实现细节。

题意

给一棵树,每个节点i上有一个数字$G_{i}$,q个询问,寻找x-y简单路径上数字的最大异或值。

$M=log(G_{i}) \leq 60$

分析

由最大异或值可以想到线性基,其中插入、求异或最大值的操作都需log(x)复杂度。

x-y简单路径让我想到倍增LCA

void insert(int *a,int x)
{
    for(int i=M;i>=0;--i)
    {
        if((x >> i)&1)
        {
            if(!a[i])
            {
                a[i]=x;
                break;
            }
            else    x^=a[i];
        }
    }
}
int querymax()
{
    int ret=0;
    for(int i=M;i>=0;--i)
    {
        ret=max(ret*1,ret^ans[i]);
    }
    return ret;
}

对q个询问,用倍增预处理。

简单定义线性基的merge操作,$O(M^{2})$

void merge(int *a,int *b)//a <-b
{
    for(int i=0;i<=M;++i)
    {
        if(b[i])
        {
            insert(a,b[i]);
        }
    }
}

线性基的倍增

用三维数组p表示线性基,p[i][j]包含i到f[i][j]的线性基(左闭右开)。