TJUACM 2024暑期集训(一)7.8-7.14

强度逐渐开始拉满,不敢想象多校赛正式开始后有多逆天。

SW1-1 Maximal Submatrix

题目信息

给定一个的矩阵,求其最大的子矩形面积,使得子矩形的每一列单调不减。

题目解析

每一列单调不减,则暴力维护每一列小矩形的高度表示从第行开始向下最多到什么地方时连续单调不减的。

此时就将问题转化成了放置在同一水平线上的个高低不一的矩形柱所能围出的最大子矩阵面积。

枚举每一个柱子的高度,分别向左向右找到第一个比其小的柱子,那么这个高度的柱子所能围成的矩形的长即可决定,维护过程使用单调栈双向扫一遍就可以了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;
int a[2001][2001], h[2001][2001], l[2001], r[2001];
int count(int i)
{
    stack<int> s;
    int ans = 0;
    for (int j = 1; j <= m; j++)
    {
        while (!s.empty() && h[i][j] < h[i][s.top()])
            r[s.top()] = j, s.pop();
        s.push(j);
    }
    while (!s.empty())
        r[s.top()] = m + 1, s.pop();
    for (int j = m; j >= 1; j--)
    {
        while (!s.empty() && h[i][j] < h[i][s.top()])
            l[s.top()] = j, s.pop();
        s.push(j);
    }
    while (!s.empty())
        l[s.top()] = 0, s.pop();
    for (int j = 1; j <= m; j++)
    {
        ans = max(ans, (r[j] - l[j] - 1) * h[i][j]);
    }
    return ans;
}
int main()
{
    IOS;
    int t;
    cin >> t;
    while (t--)
    {

        cin >> n >> m;
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= m; j++)
            {
                cin >> a[i][j];
                if (a[i][j] >= a[i - 1][j])
                    h[i][j] = h[i - 1][j] + 1;
                else
                    h[i][j] = 1;
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
            ans = max(ans, count(i));
        }
        cout << ans << endl;
    }
}

SW1-2 K-D Graph

题目信息

一张图为图,当且仅当其满足以下定义:

  1. 图上的个结点可被严格的分割成大小为的非空连通分量。
  2. 每个连通分量中的所有边长度均不得大于

给定无向无环图以及分割数,求最小的

题目解析

分割数是给定的,类似于时间安排问题,思考二分,交了一发

观察得知如果两个原本属于不同组的结点如果可以划分到一组之中时,组数会减少.如果要将两个节点所在组合并,会取两边以及新加边的最值。同时,如果两个节点之间的边长度小于等于当前,则必然在同一组。

使用边结构体存图,对边权排序,每一次合并都对当前组数减,使用并查集维护组。初始时,每个结点单独一组。

现在如果已经分成了块且当前边的边权不等于上一条边的边权,则说明剩下的节点可以单独形成组,合法,break。如果边权相等,则说明两者必须在一组,并查集合并。

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10;
const int M = 5e5 + 10;
int fa[N];
int n, m, k, t;
struct edge
{
    int u, v, w;
    bool operator<(const edge &p) const
    {
        return w < p.w;
    }
} a[M];
int find(int x)
{
    if (fa[x] != x)
    {
        fa[x] = find(fa[x]);
    }
    return fa[x];
}
void solve()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= m; i++)
        cin >> a[i].u >> a[i].v >> a[i].w;
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    sort(a + 1, a + m + 1);
    int sum = n, d = 0;
    for (int i = 1; i <= m; i++)
    {
        if (sum < k)
            break;
        if (sum == k && a[i].w != a[i - 1].w)
            break;
        if (find(a[i].u) == find(a[i].v))
            continue;
        fa[find(a[i].u)] = fa[find(a[i].v)];
        d = a[i].w;
        sum--;
    }

    if (sum == k)
        cout << d << endl;
    else
        cout << -1 << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        solve();
    }
}

SW1-3 Minimum Spanning Tree

题目信息

个编号从的节点,每两个节点之间的边权为其编号的,求其最小生成树。

题目解析

首先,偶数和的最小公倍数一定是原先的偶数本身,这样连边对于偶数而言一定是最优化的。

对于剩下的奇数,如果的倍数,此时最小公倍数也是本身,同样是最优化的。

再剩下的节点就没有办法,只能和连接,换取一个比较小的倍数。

具体实现的过程就会发现,实际上每个数都去找了它最小的质因数进行连接,所有的比小的质数都要多算一次。

离线线性筛打表即可。大范围预处理然后在线查询也可。

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n, t, sum, x;
int pri[N], f;
bool vis[N];
vector<int> q;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    for (int i = 1; i <= t; i++)
    {
        cin >> x;
        q.push_back(x);
        n = max(x, n);
    }
    for (int i = 2; i <= n; i++)
    {
        if (!vis[i])
            vis[i] = 1, pri[++f] = i;
        for (int j = 1; j <= f && i * pri[j] <= n; j++)
        {
            vis[i * pri[j]] = 1;
            if (i % pri[j] == 0)
                break;
        }
    } // pri存了1-n的质数
    for (auto i : q)
    {
        if (i == 2)
        {
            cout << 0 << endl;
            continue;
        }
        sum = ((3 + i) * (i - 2)) / 2;
        // cout<<sum<<endl;
        for (int j = 2; j <= f; j++)
        {
            if (pri[j] <= i)
                sum += pri[j];
            else
                break;
        }
        cout << sum << endl;
    }
}

SW-1-4 Xor Sum

题目信息

给定一个长度为的序列,求其长度最短的子数组(连续),使得其的异或和不小于。不存在输出​,多个长度相同的答案时输出最靠前的那一个。

题目解析

区间异或和,不等式,,根存高位。

将第个异或前缀和扔进,从根开始找和异或和异或大于等于的可能的序列。

如果的第位为0,而当前走到的结点和前缀异或和在这一位不同,则异或结果为,此时不等式已经必然成立,所有包含当前在字典树中查出来的前几位的异或前缀和都满足要求;换言之,当前节点下的所有子树均满足要求。此时需要取前缀和长度最大的那一个,这说明字典树节点要维护包含其子串的所有子树的对应前缀和的最长长度。

如果选择向相同方向走,则还需要继续检查。

如果第位为,则结果固定,字典树必须向当前节点和位相同的位置继续查询。

如果始终查不到符合要求的,输出​.

时间容量吃紧,复杂度,不能使用(被卡了)。

还有一个细节,在建树的时候清理(初始化)

// #pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
using p = pair<int, int>;
#define IOS                      \
    ios::sync_with_stdio(false); \
    std::cin.tie(nullptr);       \
    std::cout.tie(nullptr);
#define int long long
const int INF = 1e18;
const int mod = 998244353;
const int N = 3e6 + 9;
int Trie[N][2];
int rem[N];
int tot = 1;
void insert(int x, int r)
{
    int u = 1;
    for (int i = 29; i >= 0; i--)
    {
        int flag = (x >> i) & 1LL;
        if (!Trie[u][flag])
        {
            Trie[u][flag] = ++tot;
            Trie[tot][0] = Trie[tot][1] = 0;
            rem[tot] = 0;
        }
        u = Trie[u][flag];
        rem[u] = max(rem[u], r);
    }
}
int check(int pos, int tar, int k)
{
    int u = 1;
    int ans = 0;
    for (int i = 29; i >= 0; i--)
    {
        int bittar = (tar >> i) & 1LL;
        int bitk = (k >> i) & 1LL;
        if (bitk)
        {
            u = Trie[u][bittar ^ 1];
        }
        else
        {
            if (Trie[u][bittar ^ 1])
            {
                ans = max(ans, rem[Trie[u][bittar ^ 1]]);
            }
            u = Trie[u][bittar];
        }
        if (!u)
            return ans;
    }
    ans = max(ans, rem[u]);
    return ans;
}

void solve()
{
    tot = 1;
    int n, k;
    cin >> n >> k;
    Trie[1][1] = Trie[1][0] = 0;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if (i)
            a[i] ^= a[i - 1];
    }
    int ans = 0;
    insert(a[1], 1);
    pair<int, int> ansf = {INF, INF};
    int len = INF;
    for (int i = 2; i <= n; i++)
    {
        ans = check(i - 1, a[i], k);
        if (ans > 0)
        {
            if (i - ans < len)
            {
                len = i - ans;
                ansf = {ans + 1, i};
            }
            else if (i - ans == len)
            {
                ansf = min(ansf, {ans + 1, i});
            }
        }

        insert(a[i], i);
    }
    if (len == INF)
    {
        cout << -1 << endl;
        return;
    }
    cout << ansf.first << " " << ansf.second << endl;
}
signed main()
{
    IOS;
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

SW-1-5 Pass!

题目信息

个人传球,初始时在号人手上。每秒可以传一次球,持球者不能传给自己。经过秒后球回到了第一个人手里的方案数为,结果对取模。

现在你有的值以及,求最小的可能,使得可以作为上述问题的结果。不存在则输出.

题目解析

如果球在传球途中不能回到号人手中,则s后求回到初始人手中的方案数为,相当于解方程

这个方程不可能没有解(因为其可构成一个完全剩余系)

所以证明球中途可以回到初始人的手中。

考虑第s时球在第一个人手中的方案数为,不在为,则显然有

故有递推方程 解方程,得到通项公式 分奇偶求解,如果都有合法解要取最小值

问题回到求解指数方程,采用离散对数的​根号算法解决,。如果使用会多一个的复杂度,本题被卡掉了。

// #pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
#define int long long
const int INF = 1e18;
const int mod = 998244353;
const int N = 36007; // N 是最大可以存储的元素数量
class Hash
{
private:
    int keys[N];
    int values[N];

public:
    Hash() { memset(values, 0, sizeof(values)); }

    int &operator[](int n)
    {
        int idx = (n % N + N) % N, cnt = 1;
        while (keys[idx] != n && values[idx] != 0)
        {
            idx = (idx + cnt * cnt) % N;
            cnt += 1;
        }
        keys[idx] = n;
        return values[idx];
    }
};
int qpow(int a, int b)
{ // 快速幂
    int ans = 1;
    while (b)
    {
        if (b & 1)
            ans = (ans * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ans;
}

int bsgs(int a, int b)
{
    Hash mp;
    if (a % mod == b % mod)
        return 1;
    if (a % mod == 0 && b)
        return -1;

    int unit = (int)ceil(sqrt(mod)), tmp = qpow(a, unit);
    for (int i = 0; i <= unit; ++i)
        mp[b] = i, b = (b * a) % mod;
    b = 1;
    for (int i = 1; i <= unit; ++i)
    {
        b = (b * tmp) % mod;
        if (mp[b])
            return i * unit - mp[b];
    }
    return -1;
}
void solve()
{
    int n, x;
    cin >> n >> x;
    if (x == 1)
    {
        cout << 0 << endl;
        return;
    }
    if (x == 0)
    {
        cout << 1 << endl;
        return;
    }
    int a = n - 1;
    int tt = (n * x + (n - 1));
    int k1 = bsgs(a, tt % mod);
    if (k1 % 2 == 0)
        k1 = -1;
    tt = (n * x - (n - 1)) + mod;
    int k2 = bsgs(a, tt % mod);
    if (k2 & 1)
        k2 = -1;
    if (k1 != -1 && k2 != -1)
        cout << min(k1, k2) << endl;
    else if (k1 != -1 && k2 == -1)
        cout << k1 << endl;
    else if (k2 != -1 && k1 == -1)
        cout << k2 << endl;
    else
        cout << -1 << endl;
    return;
}
signed main()
{
    IOS;
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

SW-1-6 ABC Puzzle

题目信息

给定一个的矩阵的侧视图和正视图(点不算),问是否存在一个这样的方格满足上述视图,并且每行每列只有一个.

Sample Input

5
ABCBC
ACAAB

Sample output

Yes
AC..B
.BA.C
C.BA.
BA.C.
..CBA

题目解析

数据很小,暴力搜索,问题是对谁进行深度搜索,状态又如何表示。

注意到比较小,总共才有不超过个格子。

对行进行搜索,保证搜索时能够拼出来合法的侧视图,这需要预处理出所有可能的排列(注意排列需要长度为),使用next_permutation函数即可。

我们只需要核实每一列是否都只有一个并且正视图正确。

每一列只有四个判定指标:当列正视图。

故使用一个位的二进制数即可完成全部的判定。每一列分掉四个二进制位,低三位表示是否使用,高一位表示正视图是否完成了合法判断。

如果最后数字归零,证明搜索成立,结果合法。

#include <bits/stdc++.h>
using namespace std;
int n;
string r, c;
vector<string> graph;
map<char, set<string>> mp;
bool flag = 0;
/*      1 1 1 1
        1 1 1 1
        1 1 1 1
        1 1 1 1
        1 1 1 1 高三位表示该列是否检查过,低1位表示对应字母是否占用
*/
int check;
void dfs(int i, int check)
{
    if (flag)
        return;
    if (i == n)
    {
        if (check == 0)
        {
            cout << "Yes" << endl;
            for (auto &nx : graph)
            {
                cout << nx << endl;
            }
            flag = 1;
        }
        return;
    }
    for (auto str : mp[r[i]])
    {
        graph.push_back(str);
        int nowcheck = check;
        bool accepted = 1;
        for (int j = 0; j < n; j++)
        {
            if (str[j] == '.')
                continue;
            int pos = 4 * j + (str[j] - 'A');
            if ((nowcheck & (1 << pos)) == 0)
            {
                accepted = 0;
                break;
            }
            nowcheck ^= (1 << pos);
            if (nowcheck & (1 << (4 * j + 3)))
            {
                if (str[j] != c[j])
                {
                    accepted = 0;
                    break;
                }
                nowcheck ^= (1 << (4 * j + 3));
            }
        }
        if (accepted)
            dfs(i + 1, nowcheck);
        graph.pop_back();
    }
}
int main()
{
    cin >> n >> r >> c;
    vector<int> abc(n);
    iota(abc.begin(), abc.end(), 0);
    // 0-2ABC,3.
    do
    {
        string s;
        char c = 0;
        for (auto p : abc)
        {
            if (p >= 0 && p <= 2)
            {
                s += char('A' + p);
                if (c == 0)
                    c = 'A' + p;
            }
            else
                s += '.';
        }
        mp[c].insert(s);

    } while (next_permutation(abc.begin(), abc.end()));
    check = (1 << (4 * n)) - 1;
    dfs(0, check);
    if (!flag)
        cout << "No" << endl;
    system("pause");
    return 0;
}

SW-1-8 Restore Graph

题目信息

有一个没有自环和重边的图,给定一个最短路径数组,要求复原出一个满足条件的图,且保证每个顶点的度不超过

题目解析

暴力即可,这里主要谈一下为什么不考虑环的问题,显然,如果存在一个个顶点的环,那么环上的每一个定点相当于都浪费了一个度没有起到任何的作用。

#include <bits/stdc++.h>
using namespace std;
using p = pair<int, int>;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
#define int long long
const int INF = 1e18;
const int mod = 998244353;
void solve()
{
    int n, k;
    cin >> n >> k;
    vector<int> a(n);
    multimap<int, int> dist;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        dist.insert({a[i], i});
    }
    vector<int> degree(n);
    int posnow;
    bool flag = 0;
    vector<pair<int, int>> rem;
    int remch = -1;
    queue<int> noww, nextt;
    for (auto [x, y] : dist) // x距离 y节点号
    {
        bool tag = 0;
        if (x - remch > 1)
        {
            cout << "-1" << endl;
            return;
        }
        if (x - remch == 1)
            tag = 1;
        remch = x;
        if (!x)
        {
            if (!flag)
            {
                flag = 1, posnow = y;
                nextt.push(y);
                continue;
            }
            else
            {
                cout << "-1" << endl;
                return;
            }
        }
        if (tag)
        {
            noww = nextt;
            nextt = queue<int>();
            if (noww.empty())
            {
                cout << "-1" << endl;
                return;
            }
            posnow = noww.front();
            noww.pop();
        }
        if (degree[posnow] >= k)
        {
            if (noww.empty())
            {
                cout << "-1" << endl;
                return;
            }
            posnow = noww.front();
            noww.pop();
        }
        degree[posnow]++;
        degree[y]++;
        rem.push_back({posnow, y});
        nextt.push(y);
    }
    cout << rem.size() << endl;
    for (auto [x, y] : rem)
    {
        cout << x + 1 << " " << y + 1 << endl;
    }
    return;
}
signed main()
{
    IOS;
    int t;
    t = 1;
    while (t--)
    {
        solve();
    }
    system("pause");
    return 0;
}

SW-1-9 Fake Plastic Trees

题目信息

给我们一棵有根的树,它由 个顶点组成,这些顶点的编号从 。树的根是顶点 ,顶点 的父顶点是

每个顶点上都写有一个数字,最初所有数字都等于 。我们把写在顶点 上的数字记为

对于每个 ,我们希望 位于 之间。 .

在一次操作中,我们可以完成以下操作:

  • 选择顶点 。让 成为从顶点 到顶点 的路径上的顶点(指 )。
  • 选择长度为 的非负整数数组
  • 对于每个 增加

实现我们的目标至少需要多少次运算?

题目解析

正着想很难想,考虑倒着从叶子结点开始考虑。

贪心的想,每一个叶子结点都是流满最大流量的,此时可以使得其前序序列所能经过的流量尽可能的大。

对于叶子结点的父节点,如果其叶子结点的流量和大于等于其的上界,则其分配的流量,并视作叶子结点继续上传。

如果叶子结点的流量和在其范围之内,那暂认为其已经符合要求,将其叶子结点流量和作为该父节点的流量需求上传。

如果叶子结点流量和不够,则说明其必须需要补充流量,那每一次补充都要利用到极致,所以直接把该父节点补满,总和记作的流量分配,然后上传。

即可

#include <bits/stdc++.h>
using namespace std;
using p = pair<int, int>;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
#define int long long
const int INF = 1e18;
const int mod = 998244353;
const int N = 1e5 + 5;
vector<vector<int>> connects;
vector<bool> vis;
vector<pair<int, int>> information;
void add_edge(int u, int v)
{
    connects[u].push_back(v);
    connects[v].push_back(u);
}
int ans = 0;
void init(int n)
{
    ans = 0;
    connects = vector<vector<int>>(n + 1);
    vis = vector<bool>(n + 1);
    information = vector<pair<int, int>>(n + 1);
    for (int i = 0; i <= n; i++)
    {
        vis[i] = false;
        information[i] = {0, 0};
    }
}
int dfs(int pos)
{
    vis[pos] = true;
    int res = 0;
    for (auto i : connects[pos])
    {
        if (!vis[i])
        {
            res += dfs(i);
            vis[i] = 0;
        }
    }
    if (!res) // 叶子结点
    {
        ans++;
        return information[pos].second;
    }
    if (res < information[pos].first)
    {
        ans++;
        return information[pos].second;
    }
    if (res > information[pos].second)
    {
        return information[pos].second;
    }
    return res;
}
void solve()
{
    int n;
    cin >> n;
    init(n);
    for (int i = 2; i <= n; i++)
    {
        int u;
        cin >> u;
        add_edge(i, u);
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> information[i].first >> information[i].second;
    }
    dfs(1);
    cout << ans << endl;
}
signed main()
{
    IOS;
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    // system("pause");
    return 0;
}

SW-1-10 Keshi in Search of AmShZ

题目信息

AmShZ 已从伊朗前往意大利参加 Thom Yorke 演唱会。意大利有 个城市,索引范围从 ,有 定向道路,索引范围从 。从 定向道路。起初,Keshi 位于城市 ,想要去 AmShZ 位于城市 的家。由于 Keshi 不知道意大利的地图,AmShZ 帮助他尽快见到对方。

每天一开始,AmShZ 可以向 Keshi 发送以下两条信息中的一条:

  • AmShZ 向 Keshi 发送一条道路的索引,将其作为被封锁的道路。然后 Keshi 就会明白,他永远都不应该使用这条路,他将在当前城市停留一天。

  • AmShZ 告诉凯希移动。然后,凯西会随机选择一个可以从当前城市到达的城市,并移动到那里。(如果从城市 到城市 有一条尚未被封锁的出城道路,那么城市 可以到达城市 )。如果没有这样的城市,凯西将留在当前城市。

    请注意,AmShZ 总是知道 Keshi 的当前位置。

AmShZ 和 Keshi 想要找到一个最小的整数 ,在这个整数中,他们可以确保最多在 天之后就能见到对方。请帮助他们找到

题目解析

设来访者此时站在结点,该节点有条出边。如果不加以任何干涉的话,因为要求最小的最大值,也就是求最小的最坏情况,其最坏情况必然是选择向后续移动时间更长的那个城市进行移动。换言之,目标一直尝试走最长路径。而想要让其走某条比较短路径的方法就是花费一定的代价断掉那条最长的边。

一个朴素的想法就是断掉除指定路径外所有的边,将其作为边权的一部分跑最短路,但是这样有一个问题,其需要找到所有的符合条件的最短路并且保存其路径,寻找路径的交点来调整断边(因为可能多断了没必要的边),这样很麻烦。

换一种思路来想,如果来访者站在节点,那么从结点到终点的最坏时间最小值应当取什么? 也就是说,如果最坏打算时要走第小的边,那就必须保证后条边全部断掉。

如果有相等边权的边的话,因为取值,一定会取到最小的.

所以我们从小到大拿的儿子的尝试去更新的值。

这就是队列优化​的过程

因为就是每次拿最小的结点尝试去更新下一个点的最短路进长度。

所以对题目进行反向建图,通过​约束模拟过程即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int INF = 1e18;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
vector<vector<int>> connects;
vector<int> dist;
vector<int> degrees;
vector<int> visited;
void init(int n)
{
    n++;
    connects.resize(n, vector<int>());
    dist.resize(n, INF);
    degrees.resize(n, 0);
    visited.resize(n, 0);
}
void add_edge(int u, int v)
{
    connects[u].push_back(v);
    degrees[v]++;
}
int Dijkstra_queue(int st, int ed) // 反向Dijkstra
{
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
    q.push({0, ed});
    dist[ed] = 0;
    while (!q.empty())
    {
        auto [d, u] = q.top();
        q.pop();
        if (visited[u])
            continue;
        visited[u] = true;
        for (auto v : connects[u])
        {
            if (dist[v] > dist[u] + degrees[v])
            {
                dist[v] = dist[u] + degrees[v];
                q.push({dist[v], v});
            }
            degrees[v]--;
        }
    }
    return dist[st];
}
void solve()
{
    int n, m;
    cin >> n >> m;
    init(n);
    for (int i = 0; i < m; i++)
    {
        int u, v;
        cin >> u >> v;
        add_edge(v, u);
    }
    cout << Dijkstra_queue(1, n) << endl;
    return;
}
signed main()
{
    IOS;
    int t;
    t = 1;
    while (t--)
        solve();
    return 0;
}

SW-1-EX-10 骑士游戏

题目信息

一共有种怪兽,每种怪兽的法术斩杀伤害为,物理击败伤害为。每种怪兽被物理击败会分裂成其他怪兽(包括自身)。现在是​号怪兽入侵,问消灭全部怪兽最少需要总计多少伤害?

题目解析

类似于上题,从某个怪兽入手,其最小的灭绝伤害为

但是有个问题在于,这个方程是有后效性的,因为可能有自环,形成自身依赖。所以,我们尝试把后决条件改变为先决条件。

如果一个点其儿子的所有最小伤害都确定且和物理伤害加起来都没有法术斩杀伤害高,那显然选择使用后者。

此时,怪兽的最低灭绝伤害被确定,其可作为一个儿子去贡献其父亲结点的决策。

也就是说,一个确定的结点的充分必要条件,是其确定值为法术伤害,或者其所有的儿子节点都已经确定。

所以对于每一个节点,我们总是尝试用其最小的未触探过的儿子去触探它,当所有儿子都确定之后,父节点亦可决定。(可以剪枝优化,如果目前为止物理伤害所需伤害就已经高于法术伤害的话,直接更新为即可。)

这就又是的过程,反向建图倒着扫一遍就可以,对于每一个节点确定的时候,将其入堆,堆中弹出的一定是当前堆中确定好的最小的消灭怪兽节点。

问题是,初始状态是谁?这里显然没有明确的起点,也不可以建立一个大型超级终点,因为入堆的方式严格要求入堆点的入度为

哦我们考虑一个问题,因为每个怪兽被物理攻击后必然会分裂,没有物理攻击就能直接消灭的怪兽,所以一定有至少一个怪兽必须使用法术伤害消灭。那么这些必须使用法术伤害消灭的一定是比较小的(法术伤害最小的那个怪兽一定是直接法术斩杀,因为其不可能放弃最小化的方式去使用物理伤害兜圈)

所以法术伤害最小的那个节点状态一定是确定的,其必须作为起点状态入堆。

问题是,只有这一个起点对吗?

显然不对,因为只用这一个法术伤害斩杀目标,此时必然存在一种可能,其更新不动了,没有新节点入堆,队列变空,和超级终点的问题一样了。

所以,需要将所有节点的法术伤害全部入队列,保证更新不动的时候会选择更多的怪兽直接使用法术伤害斩杀。而且由于标记,如果某节点有更优的不适用法术伤害斩杀的方式,其必然是那种方式优先被堆弹出,不被影响。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
const int maxn = 2e5 + 10;
vector<int> connects[maxn];
int s[maxn], k[maxn], degree[maxn], dp[maxn];
int n;
bool vis[maxn];
void add_edge(int u, int v)
{
    connects[u].push_back(v);
}
typedef struct node
{
    int pos;
    int dis;
    bool operator<(const node &x) const
    {
        return x.dis < dis;
    }
} node;
void Dikstra_queue()
{
    priority_queue<node> q;
    for (int i = 1; i <= n; i++)
    {
        q.push({i, dp[i]}); // 初始化,预先把所有的法术伤害作为杀死方式预选择丢进去
        // 如果某个怪物的法术斩杀伤害是最小的,那么这个怪兽必然是使用法术斩杀的,其不可能再去使用物理伤害兜圈
        // 为什么全丢进去,是当某个节点的物理伤害计算发现其比法术斩杀还要大,那这个点就是用法术斩杀打死的
        // 如果最小的法术伤害绕着走了一圈之后没有发生新入队行为,那么就更新不动了,队列变空,
        //则说明如果某些可能度数没有归零的点的怪兽想使用物理伤害杀死,必须使用法术伤害干死的怪兽不止一个
        //答案就错误了,所以必须把所有的都丢进去以初始化才可以
    }
    while (!q.empty())
    {
        auto [pos, dpp] = q.top();
        q.pop();
        if (vis[pos])
            continue;
        vis[pos] = 1;
        dp[pos] = dpp; // 固定杀死方式
        for (auto v : connects[pos])
        {
            if (vis[v]) // 已经固定杀死方式,即dp值已确定
                continue;
            if (s[v] + dpp >= dp[v]) // 还没加完的物理攻击就已经比dp值大,此时不更新
                continue;
            s[v] += dpp;
            degree[v]--;
            if (!degree[v])
            {
                q.push({v, s[v]});
            }
        }
    }
    cout << dp[1] << endl;
}
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
signed main()
{
    IOS;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i] >> k[i] >> degree[i];
        dp[i] = k[i];
        for (int j = 1; j <= degree[i]; j++)
        {
            int x;
            cin >> x;
            add_edge(x, i);
        }
    }
    Dikstra_queue();
    system("pause");
    return 0;
}

SW-1-11 Guess the String

题目信息

这是一个交互问题。

你需要去猜一个长度为的字符串串,你可以提两种问题。

  1. 询问? 1 i,评测机返回第个位置的字符是谁(
  2. 询问? 2 l r,评测机返回区间之间不同字符的个数。

第一个问题不允许超过次,第二个问题不允许超过​次。

题目解析

次询问,显然是询问时如果返回值发生了变化,说明第位置是一个新字符。

此次遍历最坏花费了次操作次操作.

剩余约为,考虑对字符二分询问。

记两个新字符第一次出现的位置为,则区间内只会出现前种字符。

判断每一位的字符是谁的唯一方式(因为询问已被使用完)是找到前种字符每一个字符的最后一次出现的位置,通过询问,如果两个位置的询问结果(知道~ 的所有字符,预处理即可)一样,那么显然第位的字符的最后出现位置在之后,否则在​之前。

暴力每一个,对前种字符最后一次出现的位置排序后二分询问,则可以实现每一次二分只询问一次,每一个位置只询问单次。

#include <bits/stdc++.h>
using namespace std;
char ask_one(int i)
{
    cout << "? 1 " << i << endl;
    char c;
    cin >> c;
    fflush(stdout);
    fflush(stdin);
    return c;
}
int ask_line(int l, int r)
{
    cout << "? 2 " << l << " " << r << endl;
    int x;
    cin >> x;
    fflush(stdout);
    fflush(stdin);
    return x;
}
int main()
{
    int n;
    cin >> n;
    fflush(stdout);
    fflush(stdin);
    vector<int> remrangedif;
    string s;
    s += ask_one(1);
    remrangedif.push_back(1);
    for (int i = 1; i < n; i++)
    {
        int size = ask_line(1, i + 1);
        if (size > remrangedif[0])
        {
            s += ask_one(i + 1);
        }
        else
        {
            map<char, int> last;
            for (int j = 0; j < s.size(); j++)
                last[s[j]] = j; // 记录每个字母最后出现的位置
            vector<int> lasts;
            for (auto x : last)
                lasts.push_back(x.second);
            sort(lasts.begin(), lasts.end()); // 最后出现的位置排序
            int l = 0;
            int r = lasts.size();
            while (r - l > 1)
            {
                int m = (l + r) >> 1LL;
                int c = ask_line(lasts[m] + 1, i + 1);
                if (c == remrangedif[lasts[m]])
                    l = m;
                else
                    r = m;
            }
            s += s[lasts[l]];
            remrangedif.push_back({size});
        }
        remrangedif = vector<int>(i + 1);
        map<char, int> q;
        for (int j = i; j >= 0; j--)
        {
            q[s[j]]++;
            remrangedif[j] = q.size();
        }
    }
    cout << "! " << s << endl;
    fflush(stdout);
    fflush(stdin);
    return 0;
}

SW-1-12 SosDp(模板题)

题目信息

给定,求 定义位二进制数的包含关系

即如果的某一位是,则无所谓;的某一位是的话,对应位必须是​​​。

等价于.

题目解析

考虑朴素的枚举每一个的子集,是高维前缀和的本质,时间复杂度

实际上我们可以转化一下模型。对于一个数我们将其转化为二进制,以为例。我们发现,我们可以将这个数字看成一个维的坐标,然后该数的权值就是这个坐标的权值,那么对于来说,我们要统计的就是相当于所有坐标中满足每一维度的坐标都小于等于每一维度的坐标的权值和。那么这就是个维偏序,我们可以用高维前缀和解决。

for(int j = 0; j < n; j++) 
    for(int i = 0; i < 1 << n; i++)
        if(i >> j & 1) f[i] += f[i ^ (1 << j)];

如果要维护,即维护,给定时即维护超集,同理,只需要改个判定条件。

for(int j = 0; j < n; j++) 
    for(int i = 0; i < 1 << n; i++)
        if(!(i >> j & 1)) f[i] += f[i ^ (1 << j)];

SW-1-EX-12 I love max and multiply

题目信息

给定两个长度为,编号为的数列,定义,求

题目解析

不好处理,考虑使用更为严格的小于关系

​的充分不必要条件

则有,由定义可推出。

考虑

故可分开成两个集合

分别维护子集的最大和最小值即可,注意这里维护的是超集(给定)

考虑原集合

故有

#include <bits/stdc++.h>
using namespace std;
using p = pair<int, int>;
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
#define int long long
const int INF = 1e18;
const int mod = 998244353;
void solve()
{
    int n;
    cin >> n;
    int m = 1;
    while (m<n)
        m<<=1LL;
    vector<int> a(n), b(n), c(m+1, -INF), maxa(m, -INF), maxb(m, -INF), mina(m, INF), minb(m, INF);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        maxa[i] = mina[i] = a[i];
    }
    for (int i = 0; i < n; i++)
    {
        cin >> b[i];
        maxb[i] = minb[i] = b[i];
    }
    for(int i=n;i<m;i++)
    {
        maxa[i]=maxb[i]=-INF;
        mina[i]=minb[i]=INF;
    }
    for(int j=0;(1LL<<j)<m;j++)
    {
        for(int i=0;i<=n-1;i++)
        {
            if(!(i>>j&1LL))
            {
                maxa[i]=max(maxa[i],maxa[i^(1LL<<j)]);
            maxb[i]=max(maxb[i],maxb[i^(1LL<<j)]);
            mina[i]=min(mina[i],mina[i^(1LL<<j)]);
            minb[i]=min(minb[i],minb[i^(1LL<<j)]);
            
            }
        }
    }
    int ans=0;
    for (int i = n - 1; i >= 0; i--)
    {
                 c[i]=max(max(maxa[i]*maxb[i],mina[i]*minb[i]),max(maxa[i]*minb[i],maxb[i]*mina[i]));
        c[i] = max(c[i], c[i + 1]);
        (ans += (mod + c[i])) %= mod;
    }
    cout << (ans % mod + mod) % mod << endl;
}
signed main()
{
    IOS;
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    // system("pause");
    return 0;
}

SW-1-13 I love tree

题目信息

给定一棵结点的树,询问次,初始时树的节点都为,支持两种操作

  1. 1 a b查询的路径,并将路径上第个点加
  2. 2 a查询当前​点的值

题目解析

树链剖分模板题,找到的最近公共祖先后分两段讨论深度的差值的平方,将其转化为二次函数,开三颗线段树维护即可。

注意,​时跳的结点是其所在重链的链头深的向上跳。

注意,此题目如下写法会炸邻接表,需要使用链式前向星进行存边,而且还好写

// #pragma GCC optimize(2)
#pragma comment(linker, "/STACK:10240000000,10240000000")
#include <Bits/stdc++.h>
//#define DEBUG 1
using namespace std;
#define int long long
#define ll long long
const int maxn = 2e5 + 9;
struct BITtree
{
    ll n;
    vector<ll> Bit;
    BITtree(ll n) : n(n), Bit(vector<ll>(n+1, 0)) {}
    BITtree() { BITtree(0); }
    ll lowBit(ll x) { return (x & (-x)); }
    void update(ll pos, ll x)
    {
        for (; pos <= n; pos += lowBit(pos))
            Bit[pos] += x;
    }
    ll query(ll pos)
    {
        ll ans = 0;
        for (; pos; pos -= lowBit(pos))
            ans += Bit[pos];
        return ans;
    }
};
int father[maxn], sizes[maxn], hson[maxn], depth[maxn], top[maxn], ranks[maxn], dfn[maxn], cnt;
int nxt[maxn << 1LL], head[maxn], to[maxn << 1LL], tot;
void add(int u, int v)
{
    nxt[++tot] = head[u];
    head[u] = tot;
    to[tot] = v;
}
void add_edge(int u, int v)
{
    add(u, v);
    add(v, u);
}
void dfs1(int pos)
{
    hson[pos] = -1;
    sizes[pos] = 1;
#ifdef DEBUG
    cout << endl;
    cout << "-----------DEBUG-----------" << endl;
    cout << "now pos is " << pos << endl;
    cout << "now sons are ";
    for (int i = head[pos]; i; i = nxt[i])
    {
        cout << to[i] << " ";
    }
    cout << endl;
    cout << "-----------DEBUG-----------" << endl;
#endif
    for (int i = head[pos]; i; i = nxt[i])
    {
        if (!depth[to[i]])
        {
            depth[to[i]] = depth[pos] + 1;
            father[to[i]] = pos;
            dfs1(to[i]);
            sizes[pos] += sizes[to[i]];
            if (hson[pos] == -1 || sizes[to[i]] > sizes[hson[pos]])
                hson[pos] = to[i];
        }
    }
}
void dfs2(int u, int tops)
{
    top[u] = tops;
    dfn[u] = ++cnt;
    ranks[cnt] = u;
    if (hson[u] != -1)
    {
        dfs2(hson[u], tops);
        for (int i = head[u]; i; i = nxt[i])
        {
            if (to[i] == father[u] || to[i] == hson[u])
                continue;
            dfs2(to[i], to[i]);
        }
    }
}
int lca(int u, int v)
{
    while (top[u] != top[v])
    {
        if (depth[top[u]] < depth[top[v]])
            swap(u, v);
        u = father[top[u]]; // 所在重链首低的向上跳
    }
    return depth[u] < depth[v] ? u : v;
}
BITtree tree1, tree2, tree3;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    //freopen("1002.in", "r", stdin);
    //freopen("1002.myout", "w", stdout);
    int n;
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        add_edge(u, v);
    }
    depth[1] = 1;
    dfs1(1);
    dfs2(1, 1);
#ifdef DEBUG
    for (int i = 1; i <= n; i++)
        printf("%lld ", dfn[i]);
    printf("\n");
    for (int i = 1; i <= n; i++)
        printf("%lld ", top[i]);
    printf("\n");
    for (int i = 1; i <= n; i++)
        printf("%lld ", depth[i]);
    printf("\n");
#endif
    tree1 = BITtree(n);
    tree2 = BITtree(n);
    tree3 = BITtree(n);
    int q;
    cin >> q;
    while (q--)
    {
        int op, u, v;
        cin >> op >> u;
        if (op == 1)
        {
            cin >> v;
            int lcas = lca(u, v);
            int s = u;
            int l, r;
            int k1 = -depth[u] - 1;
#ifdef DEBUG
            printf("lca:%lld\n", lcas);
            printf("case1\n");
            printf("k1:%lld\n", k1);
#endif
            while (top[s] != top[lcas]) // s向上跳链,(s,top[s])必然连续
            {
                l = dfn[top[s]], r = dfn[s];
#ifdef DEBUG
                printf("l:%lld r:%lld\n", l, r);
                printf("s:%lld top[s]:%lld\n", s, top[s]);
#endif
                tree1.update(l, 1);
                tree1.update(r + 1, -1);
                tree2.update(l, 2 * k1);
                tree2.update(r + 1, -2 * k1);
                tree3.update(l, k1 * k1);
                tree3.update(r + 1, -k1 * k1);
                s = father[top[s]];
            }
            int q = v;
            int k2 = depth[u] - 2 * depth[lcas] + 1;
#ifdef DEBUG
            printf("case2\n");
            printf("k2:%lld\n", k2);
#endif
            while (top[q] != top[lcas])
            {
                l = dfn[top[q]], r = dfn[q];
#ifdef DEBUG
                printf("l:%lld r:%lld\n", l, r);
                printf("q:%lld top[q]:%lld\n", q, top[q]);
#endif
                tree1.update(l, 1);
                tree1.update(r + 1, -1);
                tree2.update(l, 2 * k2);
                tree2.update(r + 1, -2 * k2);
                tree3.update(l, k2 * k2);
                tree3.update(r + 1, -k2 * k2);
                q = father[top[q]];
            }
            l = dfn[s], r = dfn[q];
#ifdef DEBUG
            printf("l:%lld r:%lld\n", l, r);
            printf("s:%lld q:%lld\n", s, q);
#endif
            if (l <= r)
            {
                tree1.update(l, 1);
                tree1.update(r + 1, -1);
                tree2.update(l, 2 * k2);
                tree2.update(r + 1, -2 * k2);
                tree3.update(l, k2 * k2);
                tree3.update(r + 1, -k2 * k2);
            }
            else
            {
                tree1.update(r, 1);
                tree1.update(l + 1, -1);
                tree2.update(r, 2 * k1);
                tree2.update(l + 1, -2 * k1);
                tree3.update(r, k1 * k1);
                tree3.update(l + 1, -k1 * k1);
            }
        }
        else
        {
            ll ans = 0;
#ifdef DEBUG
            printf("ans\n");
            printf("dfn:%lld\n", dfn[u]);
            printf("tree1:%lld\n", tree1.query(dfn[u]));
            printf("tree2:%lld\n", tree2.query(dfn[u]));
            printf("tree3:%lld\n", tree3.query(dfn[u]));
#endif
            ans += 1LL * tree1.query(dfn[u]) * depth[u] * depth[u];
            ans += 1LL * tree2.query(dfn[u]) * depth[u];
            ans += 1LL * tree3.query(dfn[u]);
            cout << ans << endl;
        }
    }
    // system("pause");
    return 0;
}

SW-1-S-1 I love permutation

题目信息

给定一个数和一个质数,定义生成数列,求生成数列的逆序数,,答案对取模。

题目解析

显然生成数列必然构成了一个的不含的剩余系,形成一个的排列。

问题就转换成了计算排列的符号。

根据高等代数定义,排列的符号为对换的数量。

则排列中有 如果是一个对换,那么显然,否则为.

而排列必然可以写成一系列对换的乘积,根据乘法交换性可以得知上述等式必然成立。

,上式子化简为 化简有

#include <bits/stdc++.h>
using namespace std;
#define int __int128_t
int quickpow(int a, int b, int p)
{
    int res = 1;
    while (b)
    {
        if (b & 1)
            res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}
inline void read(int &x)
{
    int f = 1;
    x = 0;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    x *= f;
}
inline void write(__int128_t x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
void solve()
{
    int a, p;
    read(a), read(p);
    int ans = quickpow(a, (p - 1) / 2, p);
    if (ans == 1)
        cout << 0 << endl;
    else
        cout << 1 << endl;
}
signed main()
{
    int t;
    read(t);
    while (t--)
        solve();
    return 0;
}

SW1-S-2 Pty loves lcm

题目信息

定义.

给定其中表示欧拉函数,中括号项表示布尔值。

数据包括多组测试样例,保证每组不多于个。

题目解析

棘手的问题。

由于多项没有通用的公式,且注意到其要求必须连续,和上一次个人赛那个序列计数思考方向不太一样。这个题的顺序是固定好的。

暴力枚举左右的连续,发现其大概达到时就已经远远超出​的上界。

考虑两个的,由于(辗转相除法定义),的最大上界为.

考虑连续三个数字的情况,连续三个数字,同时能被整除的最多有两个,故

考虑连续四个数字的情况,显然,必然有两个被同时整除的,并且至多有两个同时被整除的。故

以此类推,连续序列的估计值为,所以超过两个的连续序列在范围内的最多有个。

剩下的连续部分显然不能直接在程序里跑出来,的范围无法支持,但是我们可以将其分成块,每一块是的数据的和,这样个数大概是,完全可以在源代码中提交。至于非完整块部分,保留打表时的函数接口,直接调用部分即可,这样保证每一次暴力都是不超过。要分段打表质数,就涉及到了区间筛,区间筛必须使用埃式筛法解决。

假定要筛除区间的元素,则首先要使用线性筛筛出的全部小质数,然后在区间内筛除其对应倍数,倍数范围,向上取整方式为(l+p-1)/p.

然后处理各个数的欧拉函数,这个是个很技巧的问题,因为埃氏筛法不可以使用欧拉函数性实现递推。解决方法是使用欧拉函数的定义式。 所以要在埃氏筛法的同时维护目标数的质因数分解。由于目标数字的最小质因数不可能超过,且大于的质数最多只有一个,所以必然能够全部考察到。对每一个数字初始化为,然后筛除时分解,如果剩余数字大于​,则必然是唯一的那个大质数。

暴力维护个块的表,注意区间是左闭右开的以及精度问题。最后要注意的是,本题是对取模,这意味着不需要使用%,直接使用unsigned int自然溢出即可,否则会。还有一个问题是因为时间吃紧,不建议封装结构体以及使用#define int long long

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int mod = (1LL << 32);
map<int, int> phi;
map<int, int> smallphi;
const int maxn = 1e6 + 10;
int prime[maxn], smallprime[maxn], cnt, smallcnt;
bool is_prime[maxn], is_smallprime[maxn];
void init(int l, int r)
{
    is_smallprime[1] = 0;
    for (int i = 2; i * i <= r; i++)
    {
        if (!is_smallprime[i])
        {
            smallprime[++smallcnt] = i;
            is_smallprime[i] = 1;
            smallphi[i] = i - 1;
        }
        for (int j = 1; j <= smallcnt && i * smallprime[j] <= r; i++)
        {
            is_smallprime[i * smallprime[j]] = 0;
            smallphi[i * smallprime[j]] = smallphi[i] * smallphi[smallprime[j]];
            if (i % smallprime[j] == 0)
            {
                smallphi[i * smallprime[j]] = smallprime[j] * smallphi[i];
                break;
            }
        }
    }
}
int interval_phi(int l, int r)
{
    vector<int> vis(r - l + 1), phi(r - l + 1);
    // 区间筛,vis[i]表示i+l的值,phi[i]表示i+l的欧拉函数值
    // 初始化,通过vis判断目标是否为质数,模拟质因数分解过程;phi运用欧拉函数公式phi(n)=n*\prod_{p|n}(1-1/p)
    for (int i = 0; i <= r - l; i++)
    {
        vis[i] = i + l;
        phi[i] = i + l;
    }
    // 区间筛,枚举小质数,对区间内的数进行筛选,若vis[i]是smallprime[j]的倍数,则phi[i]=phi[i]/smallprime[j]*(smallprime[j]-1)
    for (int j = 1; j <= smallcnt && smallprime[j] * smallprime[j] <= r; j++)
    {
        for (int i = max(2ll, (l + smallprime[j] - 1) / smallprime[j]); i * smallprime[j] <= r; i++)
        {
            if (vis[i * smallprime[j] - l] % smallprime[j] == 0)
            {
                phi[i * smallprime[j] - l] = phi[i * smallprime[j] - l] / smallprime[j] * (smallprime[j] - 1);
                while (vis[i * smallprime[j] - l] % smallprime[j] == 0)
                    vis[i * smallprime[j] - l] /= smallprime[j];
            }
        }
    }
    for (int i = 0; i <= r - l; i++)
    {
        if (vis[i] > 1)
        {
            phi[i] = phi[i] / vis[i] * (vis[i] - 1);
        }
    }
    int ans = 0;
    for (int i = 0; i < r - l; i++)
    {
        (ans += (phi[i] * phi[i + 1])) %= mod;
    }
    return ans;
}
signed main()
{
    int t = 1000;
    freopen("6biao.txt", "w", stdout);
    init(1, 1e9);
    vector<int> ans(1001);
    for (int i = 0; i < t; i++)
    {
        int l, r;
        l = 1e6 * (i);
        r = 1e6 * (i + 1);
        ans[i + 1] = (interval_phi(l, r));
        (ans[i + 1] += ans[i]) %= mod;
        cout << ans[i + 1] << endl;
        fflush(stdout);
    }
    return 0;
}

由于调和级数原理,时间复杂度

然后暴力预处理个连续区间的,程序中暴力,然后结束。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned int ui;
const int maxn = 3e6 + 10;
int prime[maxn];
bool not_prime[maxn];
int phi[maxn];
int cnt = 0;
int Prime[maxn], Phi[maxn];
int Vis[maxn];

void PreEulerTable(int r)
{
    phi[1] = 1, not_prime[1] = 0;
    for (int i = 2; i <= r; i++)
    {
        int temp = cnt;
        if (!not_prime[i])
        {
            prime[++cnt] = i;
            phi[i] = i - 1;
        }
        for (int j = 1; j <= cnt && (i * prime[j] <= r); j++)
        {
            not_prime[i * prime[j]] = 1;
            phi[i * prime[j]] = phi[i] * phi[prime[j]];
            if (i % prime[j] == 0)
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
        }
    }
}

ui query(int l, int r) //[l(l+1),r(r+1) )
{
    ui ans = 0;
    for (int i = 0; i <= r - l; i++)
    {
        Vis[i] = Phi[i] = i + l;
    }
    for (int i = 1; i <= cnt && (prime[i] * prime[i] <= r); i++)
    {
        for (int j = max(2, (l + prime[i] - 1) / prime[i]) * prime[i]; j <= r; j += prime[i])
        {
            if (Vis[j - l] % prime[i] == 0)
            {
                Phi[j - l] = Phi[j - l] / prime[i] * (prime[i] - 1);
                while (Vis[j - l] % prime[i] == 0)
                    Vis[j - l] /= prime[i];
            }
        }
    }
    for (int i = 0; i <= r - l; i++)
    {
        if (Vis[i] > 1)
            Phi[i] = Phi[i] / Vis[i] * (Vis[i] - 1);
    }
    for (int i = 0; i < r - l; i++)
    {
        ans += ((ll)Phi[i] * Phi[i + 1]);
    }
    return ans;
}
vector<pair<ll, ui>> bigans;
void pre()
{
    for (int i = 1; i < 2e6; i++)
    {
        ll now = (ll)i * (i + 1), phi_ = (ll)phi[i] * (ll)phi[i + 1];
        if (__int128_t(now) * (i + 2) > 2e18)
            break;
        for (int j = i + 2; j < 2e6; j++)
        {
            ll gcd1 = __gcd(now, ll(j));
            ll tem = j / gcd1;
            if (__int128_t(now) * tem > 1e18)
                break;
            ll gcd2 = __gcd(now, tem);
            now *= tem;
            phi_ *= (phi[tem] * gcd2 / phi[gcd2]); // 精度炸了
            ui rem = phi_;
            bigans.push_back({now, rem});
        }
    }
    sort(bigans.begin(), bigans.end());
    int l = bigans.size();
    // cout << l << endl;
    for (int i = l - 2; i >= 0; i--)
    {
        bigans[i].second += bigans[i + 1].second;
    }
    return;
}
ui table[1001] = {0, 2298992963, 20737083, 513597167, 1721646463, 2806397395, 2802122835, 1485983503, 3091413427, 4208310523, 2831606407, 1628710319, 2817038135, 928385067, 3863807535, 3676485623, 767875647, 2313059015, 1137679023, 396967215, 2304634307, 2890862199, 90066623, 558603471, 1679200987, 3911243011, 982845519, 1670422743, 4093650807, 2151241015, 863633819, 1914418175, 1180229931, 890457979, 2725678339, 1513164495, 716799111, 1262781239, 3686770103, 1121843051, 1078090151, 834258119, 418144215, 1591360999, 1780791579, 174116147, 3241891131, 1141318943, 997398119, 3250717543, 3114793999, 3706497599, 2037260823, 3040210079, 1305807835, 1723687815, 1135940691, 3314489459, 949356871, 3081495243, 594017127, 1622159175, 1224816023, 1859397967, 2147536439, 2490547939, 2745824391, 3569126119, 209337115, 1385386507, 3287781831, 1511510295, 284723135, 4085720447, 2725134651, 3013309179, 71441799, 1556277167, 3658898795, 1085308915, 1678514911, 3456120263, 546233879, 2496369755, 193275419, 4282361863, 3775550443, 4285664399, 2099488911, 2628534215, 2432263179, 3468160367, 1671973155, 2938398207, 3540562715, 3610544975, 2888893179, 289531399, 3114637431, 518482127, 2262440907, 3616476143, 2966216843, 4251779999, 4063592799, 3698910747, 1227503963, 3503973859, 3549089463, 2110776455, 3619754571, 1083848987, 511566439, 2400343107, 509034383, 719893503, 736382147, 2821535951, 4251845767, 24346707, 1239781443, 342252751, 2529639107, 2189157819, 1693232319, 740767787, 1791916739, 1765663095, 3156691819, 1991340499, 958996291, 2135223311, 2360682779, 1586995451, 3434584923, 2940990079, 3418758963, 1372936207, 2361497419, 3062180695, 208618619, 1725733583, 2796042723, 2487121779, 2082226923, 1299273727, 2383364727, 2507532991, 2064075831, 2887773999, 91937839, 2345075095, 1160117815, 1279637083, 3022557379, 570839187, 2230177543, 4006750547, 3932849719, 1617229291, 3249563659, 2877452527, 1439624795, 1425145927, 4241556719, 2877537811, 360631835, 3147736067, 2510674783, 342347975, 1421385619, 3768199279, 1318995403, 3263133419, 1305990095, 3181020099, 3472685135, 744958171, 2157270675, 1758501899, 4258829695, 116770123, 1891184931, 3632605511, 3405999467, 2858780831, 3030614027, 2752130379, 1087607031, 3350849903, 587682867, 3301124123, 4211664671, 2369171623, 3587401147, 3947184935, 516717579, 3742202675, 1851980795, 227105651, 1391894051, 3260881755, 1009078155, 2802669691, 4033409119, 433501371, 4206426979, 3379776495, 4153948811, 644090875, 4243668359, 988525183, 1811852355, 2466848203, 3084493955, 205252071, 4229791387, 1831448975, 3735604355, 867719339, 1774274559, 1823165563, 1632967647, 1972451915, 3255949783, 2722118247, 2123587763, 1799455635, 3441860939, 693811531, 4040594327, 1742319319, 2831120575, 135486615, 1559567959, 1039915323, 3023152723, 251345179, 148567, 561704047, 1355954615, 774087803, 3389528703, 3172982151, 2010490411, 682459251, 831882811, 894211647, 485954151, 1232061267, 2425232455, 2082951859, 2856803571, 3663214727, 951129247, 1740109903, 132967471, 1617389483, 1786056099, 1957601263, 307243539, 1674840935, 2545534311, 345789087, 40261811, 2618050483, 4175315647, 3355004779, 2940366139, 3242406531, 3578944239, 3813672983, 3459271219, 22162063, 459733307, 3379387139, 3590111659, 825938783, 2159217487, 1316209887, 996150787, 890431487, 4262164051, 1214711407, 1816814699, 2335696871, 2880406955, 711958555, 2719959847, 2084011139, 4261913603, 1509182503, 1304818507, 3083699111, 2921789095, 4059129203, 2261760407, 1019563015, 691014667, 1906147835, 1293438347, 869348659, 1407176107, 1069531619, 4254787791, 4134886535, 1373096587, 2096955731, 4094999907, 690372671, 1453114163, 4028140907, 1641215219, 1350582919, 4065335079, 1534430891, 518110787, 2903886559, 1413585223, 3780207603, 1087524403, 267967491, 2274571987, 317454635, 1324996979, 1603459087, 3785641383, 2015622987, 2757435339, 2566602983, 437354795, 1244832131, 4082921635, 1279038511, 3109649059, 3956011511, 3729148359, 4006633543, 2269454211, 3100998907, 3679214471, 3567774563, 1390545435, 3722311443, 1618760531, 4263609743, 1878156847, 3496278231, 3462553203, 3935173651, 815165179, 2364603439, 3056554631, 3292704623, 1935625451, 913462431, 768192155, 3425228515, 2531263131, 3466160667, 393912671, 1216565991, 3927150203, 3959205303, 2068956867, 2839465691, 3755156651, 2713597355, 3787241259, 2296682235, 3640042063, 2998070943, 759656435, 3110694359, 3503100635, 3450134095, 2575894791, 159914251, 4243956503, 567405463, 233418683, 3518999711, 579176879, 4275154795, 809789459, 3542304003, 2086175627, 273000439, 3133213731, 3004848287, 3398657631, 2016671219, 3199768015, 1893875655, 14853507, 71338819, 1863272287, 2168945379, 2546033459, 1359997375, 2142916243, 2271557579, 147465163, 4129300455, 3176739887, 748664399, 4007139707, 3552421535, 3377517603, 1916734431, 154486351, 639997259, 2318596467, 2650661439, 880545171, 1373252063, 1181523847, 539868359, 2746625087, 3433227679, 1129816391, 4231348283, 136905363, 1037136787, 2955894967, 1287022619, 2597542539, 137931507, 1380487135, 2723278723, 2047412999, 2868363691, 3484052783, 1873140843, 100908955, 1357269667, 731950535, 2473250863, 3131911443, 2332831151, 3460816211, 2484543219, 1757051979, 4131685919, 964586567, 1491285039, 2043301583, 1022473039, 3760969783, 1339500983, 793695803, 2389607883, 27021503, 811344423, 3768866691, 1475327619, 1537286715, 928074503, 920200651, 1345655299, 3652588403, 2719602327, 1380240035, 1965531495, 2029875511, 2924974999, 4036335655, 1087524587, 2165394963, 4003451291, 1479514511, 381466771, 1870885027, 1441016415, 1878239283, 1068056663, 3112319919, 1412949931, 2963965167, 3920140127, 171582467, 2256369035, 4122074907, 2694373175, 1996786523, 2270411423, 3174163271, 2874411583, 106757447, 3239387103, 3907239579, 4207727003, 4030947407, 1383952795, 1150338279, 2545698731, 2272496159, 687969107, 4134521323, 3108649663, 3200292007, 659716963, 2075117831, 640342235, 274215331, 2081372775, 3617568015, 3404302363, 731879075, 1643428895, 3797615119, 2870549439, 177581563, 3236858451, 2246737003, 2984110335, 1663474831, 163582899, 1146596991, 2929724191, 2373264991, 4154828407, 3269559475, 1648396579, 2604009939, 2125905063, 3199930127, 3852996587, 2647886839, 3189753023, 1571583591, 179995943, 585830579, 4110245259, 4093758547, 3840380751, 769505107, 4028728695, 1857689667, 2754615215, 4200375427, 3394239015, 1915150747, 917880031, 1116941215, 762735727, 2688364067, 536358899, 1448960639, 3873544039, 3111855623, 2059627635, 2118688135, 1095427123, 2786290667, 2227284839, 1065489927, 300508263, 217263015, 1965532363, 1345493259, 2106698463, 806804979, 3324239447, 3532560043, 1877703595, 3126131995, 1185144379, 1822913023, 375985031, 3888797751, 301865295, 2327875939, 280603419, 2150203883, 1899477827, 2453635715, 4210793479, 1392572831, 3671316987, 4031741819, 1005088607, 263849407, 1357966923, 1763681923, 2844118839, 1389549243, 3328892619, 4268972667, 1275429119, 622784523, 162478771, 305516291, 251542735, 1093471599, 2021624271, 2592797463, 663454467, 4225009607, 844761039, 2445117527, 4008735419, 1081352511, 2921013895, 3809959799, 3139127099, 4221682995, 3743369327, 402177411, 672237707, 403776223, 3624409531, 3466972627, 2005484183, 2532965591, 3865638579, 1041130615, 1874038915, 1811287379, 2254730695, 508811767, 595992999, 4108318087, 3625298875, 630359535, 475749255, 3194086415, 2099453967, 1097882715, 1972564059, 3079957091, 3216907907, 2904136543, 484634527, 1297091459, 1019866439, 1213745255, 3007335523, 1022131879, 3874779863, 1040940119, 593550247, 3555293967, 1394063003, 924071791, 657194179, 3962472595, 3352620527, 1294872159, 1370862063, 3634560027, 1103517803, 1107958643, 349124207, 847127883, 2312199671, 2893449619, 2506659971, 3898131515, 1249835079, 2912291795, 3182501363, 4276031491, 3320300723, 3691841651, 1151591783, 1325770763, 2470024743, 469963155, 4228332827, 1667500119, 4084457323, 2954667663, 2451322947, 2313918667, 3635948819, 1580073807, 1795564507, 3619994167, 1251831707, 1615220455, 3476126023, 2122468015, 3670871487, 3329389339, 385609819, 490508091, 310474559, 2419444695, 3320962171, 96171267, 2486568823, 148622599, 2561244099, 524390267, 2066148279, 4055946611, 4012783139, 99197491, 1211541039, 2921074371, 3793303943, 3716259839, 3570887107, 557336387, 540761783, 2600425811, 3002405967, 1073020715, 1409510215, 2828661819, 2506318903, 1904582739, 3282820943, 2784185047, 2428315727, 2292692887, 4080760243, 633896999, 3984255811, 1370050819, 4063421839, 1788241543, 1110225231, 2676295171, 2458314115, 1928213115, 2284988875, 3535586767, 1057234903, 591661063, 1894884767, 2494178843, 4131087471, 3812370251, 575641527, 3239624555, 373227219, 510717655, 1433936423, 2701373839, 1293211495, 3374884383, 994993535, 1067694355, 4249866667, 1894009439, 1740567619, 2189724879, 2868799811, 3463490855, 32048823, 1677023555, 1013572327, 2029582383, 175190891, 151630511, 3326102735, 3985817027, 2331579159, 1049624839, 3681904483, 696652263, 935651391, 1506444955, 1566775363, 1370525479, 2234560803, 130341331, 3244166311, 1680156607, 254674911, 1471925779, 2063801675, 1408211159, 112690999, 3726065451, 208188551, 793973151, 225793343, 2935502599, 3364863203, 2820608039, 2375328423, 458675491, 3430774799, 59616611, 2482356927, 2842702655, 3275885799, 3208825903, 59710187, 1133360107, 3974203911, 2297973847, 2743180827, 889159483, 2327345111, 1153331607, 3007667683, 3565352887, 2441783259, 1811481559, 1073529675, 3890109487, 589265523, 3269380031, 1862105995, 2309457555, 2085309219, 893056351, 2319099575, 1278856675, 2375613611, 1794653915, 1159975135, 2541638007, 746129151, 1591511735, 2560819303, 3977388191, 3251906975, 3952267947, 1895404811, 584274759, 1637408751, 2690854327, 407309739, 1620754527, 2905764403, 4281873443, 48400619, 3358724631, 488252199, 3078677691, 760323355, 2880427763, 2733775119, 3491913543, 2079506659, 217997511, 3528633659, 3989811275, 3296502011, 176098511, 2102501007, 2928407091, 227350339, 2577031031, 382456547, 744074255, 2866991583, 2891639719, 3589629095, 3222774439, 809307903, 1001988503, 1287723395, 1346225471, 1392114043, 3890570475, 755060299, 1444249943, 2036157443, 1335387139, 340149119, 4170117775, 1301165967, 2123611343, 3641271703, 898389363, 3802026723, 1350710175, 2672619559, 1884231803, 3323840455, 2540403623, 1119355175, 312932375, 4178372695, 1525272155, 687494271, 3459204655, 3274511059, 978871771, 1605383191, 3567165915, 2389230131, 1006314651, 2251222499, 1727036151, 3236185863, 3254384839, 109194127, 506372019, 1353184843, 2398697359, 2716666863, 3287464503, 1276004047, 672567347, 3626206259, 4163203087, 1882450699, 3093259631, 2361324411, 2198462611, 3133881987, 327591739, 3822482575, 3746165807, 440729127, 807417139, 3686803151, 1227922575, 3766340403, 1114725911, 951122595, 2821531383, 1913888339, 143123907, 2137441143, 1758430727, 1036984595, 3560492679, 2597477535, 749944867, 697623039, 606760631, 109427875, 2256825187, 442399595, 3325133979, 3247779083, 1428587863, 1007273943, 2775212219, 3625840443, 1326343539, 3065712311, 2335827259, 3491775539, 2523097995, 706646891, 116465407, 390801527, 1426390187, 716811375, 3542005755, 2573833059, 4038479543, 757384163, 1319256883, 1458179955, 2238226051, 1350921335, 1241418267, 2765170219, 2820486863, 3124801027, 1813938967, 2335965495, 3708404463, 2742434715, 361230271, 1292997787, 153340575, 1362035371, 241705115, 770720355, 1654726463, 3003642783, 4078430667, 293560547, 311264731, 3078071995, 996512951, 2010703095, 159333415, 4254455419, 1560979811, 4059120723, 1664689803, 873247267, 2781020471, 1565811127, 4026313927, 2711484387, 2266588631, 1015494539, 3704029883, 2045552927, 787304779, 1236390259, 930877111, 2356549855, 2554649471, 3420025347, 1583360051, 502995971, 2381314331};
;
const int Block = 1e6;
ui gettable(ll tar)
{
    if (tar == 0)
        return 0;
    int l = 0, r = 1e9 + 1;
    while (r - l > 1)
    {
        int mid = (l + r) >> 1;
        if ((ll)mid * (ll)(mid + 1) <= tar)
            l = mid;
        else
            r = mid;
    }
    return table[l / Block] + query(l / Block * Block, l + 1);
}
void solve()
{
    ll l, r;
    scanf("%lld%lld", &l, &r);
    int posl = lower_bound(bigans.begin(), bigans.end(), pair<ll, ui>{l, ui(0)}) - bigans.begin();
    int posr = lower_bound(bigans.begin(), bigans.end(), pair<ll, ui>{r + 1, ui(0)}) - bigans.begin();
    ui ans = bigans[posl].second - bigans[posr].second;
    //cout << gettable(r) - gettable(l - 1) << endl;
    //cout << ans << endl;
    ans += (gettable(r) - gettable(l - 1));
    printf("%u\n", ans);
}
signed main()
{
    PreEulerTable(maxn);
    pre();
    int t;
    scanf("%d", &t);
    while (t--)
    {
        solve();
    }
}
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 栾博越
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信