『模拟赛题解』10.4 NOIP 模拟赛

10.4 NOIP 模拟赛

T1. 冤家路窄

Description

一个 \(n\) 个顶点 \(m\) 条边的无向图(顶点编号 \(1 \sim n\) ,无重边和自环),通过其每条边都需要一定时间。

T小 A 和小 B 是一对冤家,开始时小 A 位于 \(S\) 点,小 B 位于 \(T\) 点。他们同时出发,分别沿着最短路去往对方所在的点,即小 A 要去往 \(T\) 点,小 B 要去往 \(S\) 点。

需要你来设计双方的路线,使得他们不会在途中相遇(即在某个时间点上,双方即不在同一个点上,也不在同一条边上)。求符合条件的方案数对 \(10^9 + 7\) 取模的结果。

Solution

考试的时候数组开小了,无向图……

用 dijkstra 分别跑出 $S \to $ 每个点和 $T \to $ 每个点的最短路,并在途中计算最短路条数(以下称为 \(c_i\) )。

\(S \to T\) 的最短路长度为 \(d\),考虑容斥原理:用总方案数减不符合要求的。

则总的方案数为 \((c_t)^2\) 。因为走的是最短路,且边权都是正的,所以两人只会相遇一次。如果在点上相遇,则两人到达该点的时刻为 \(\dfrac{len}{2}\) ;如果在边上相遇,则到达边的两个端点的时刻必然一个 \(\ge \dfrac{len}{2}\) ,另一个 \(\le \dfrac{len}{2}\)

枚举每个点和每条边即可。

Code

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

#define int long long

const int maxn = 2e5 + 5;
const int maxm = 4e5 + 5;
const int mod = 1e9 + 7;

int n, m, s, t, cnt = 1;
int head[maxn];
int dis[maxn][2], c[maxn][2];
bool vis[maxn];
struct Edge
{
    int nxt, to, w;
} edge[maxm];

void addedge(int x, int y, int w)
{
    edge[++ cnt].to = y;
    edge[cnt].w = w;
    edge[cnt].nxt = head[x];
    head[x] = cnt;
}

void dijkstra(bool p)
{
    memset(vis, 0, sizeof vis);
    for (int i = 1; i <= n; i ++)
        dis[i][p] = 1e18;
    priority_queue < pair <int, int>, vector < pair <int, int> >, greater < pair <int, int> > > q;
    dis[p ? t : s][p] = 0;
    c[p ? t : s][p] = 1;
    q.push({0, p ? t : s});
    while (!q.empty())
    {
        int x = q.top().second;
        q.pop();
        if (vis[x])
            continue;
        vis[x] = 1;
        for (int i = head[x]; i; i = edge[i].nxt)
        {
            int y = edge[i].to;
            int w = edge[i].w;
            if (dis[y][p] > dis[x][p] + w)
            {
                dis[y][p] = dis[x][p] + w;
                c[y][p] = c[x][p];
                q.push({dis[y][p], y});
            }
            else if (dis[y][p] == dis[x][p] + w)
                c[y][p] = ((c[y][p] + c[x][p]) % mod + mod) % mod;
        }
    }
}

signed main()
{
    freopen("avoid.in", "r", stdin);
    freopen("avoid.out", "w", stdout);
    cin >> n >> m;
    cin >> s >> t;
    for (int i = 1; i <= m; i ++)
    {
        int u, v, d;
        cin >> u >> v >> d;
        addedge(u, v, d);
        addedge(v, u, d);
    }

    dijkstra(0);
    dijkstra(1);

    int ans = c[s][1] * c[s][1] % mod;
    for (int i = 2; i <= cnt; i ++)
    {
        int x = edge[i].to;
        int y = edge[i ^ 1].to;
        int w = edge[i].w;
        if (dis[x][0] + dis[y][1] + w == dis[s][1] && dis[x][0] + w > dis[y][1] && dis[y][1] + w > dis[x][0])
            ans = ((ans - c[x][0] * c[x][0] % mod * c[y][1] % mod * c[y][1] % mod) % mod + mod) % mod;
    }
    for (int i = 1; i <= n; i ++)
    {
        if (dis[i][0] + dis[i][1] == dis[s][1] && dis[i][0] == dis[i][1])
            ans = ((ans - c[i][1] * c[i][1] % mod * c[i][0] % mod * c[i][0] % mod) % mod + mod) % mod;
    }
    cout << ans;
    return 0;
}

T2. 吉利售价

Description

有一批货物,每批货物都有一个单价,如果 \(n\) 个货物捆绑在一起销售,可以卖出单价乘以 \(n\) 的总售价。

我们可能会得到尾数为相同数字的总售价,这样对销售会有帮助。现在给出货物的单价 \(b\) ,一个数字 \(d\) ,和最大售价 \(a\) ,问在捆绑后总售价不超过 \(a\) 时,总售价的尾数最多能够连续包含多少个 \(d\)

Solution

这是一道数论题。设售价的形式为 $10^k \times x + \overline{dddd\dots} $ ( \(k\) 个),问题可以简化为解同余方程 \(10^k \times x + \overline{dddd\dots} \equiv 0 \!\pmod p\)\(k\) 的取值从上界不断倒数到 \(0\) ,遇到第一个有解的位置即是答案。 同余方程 \(ax \equiv m \!\pmod p\) 可以写成 \(ax + by = m\) ,有解的充要条件是 \(\gcd(a, b) \mid m\) ,找出一个可行解以后,别的解都可以被它表示,判断有没有解落在范围内的即可,这些都是 exgcd 的经典操作。

T3. 首尾匹配

Description

\(n\) 个字符串,这些字符串仅包含 A, G, U, C 四种字母,\(m\) 次查询,每次查询给两个字符串 \(p, q\) ,试求:有多少个字符串同时以 \(p\) 为前缀,以 \(q\) 为后缀。

举个例子,GAC 存在前缀 G, GA, GAC,存在后缀 C, AC, GAC,那么我们可以说:GAC 同时存在前缀 GA 和后缀 AC

Solution

对于串 \(P\) ,对所有串建一棵 Trie,那么 \(P\) 会对应树上的一个结点,它的子树内所有点都以它为前缀,而子树内的点在 dfn 序列上是连续的,所以在一个区间内。

对于串 \(Q\) ,再对反串建一棵 Trie,那么同理也是询问一个子树内的点,这样就是询问两个子树的交。

一棵树的 dfn 序作为 \(x\) 轴,另一棵树的 dfn 序作为 \(y\) 轴,这样其实就是二维数点,离线下来一维扫描 线一维用树状数组查即可。

T4. 二分图排列

Description

给出一个 \(1\)\(n\) 的排列 \(a\) ,我们将每个数字看做一个点,如果存在 \(a_i > a_j\)\(i < j\) ,则在 \(i, j\) 之间连一条边。

最终如果得到的图为二分图,那么我们称 \(a\) 为二分图排列。二分图排列很稀有,因此给你一些机会,你可以选择 \(a\) 中的一些数字,把值变为 \(a_i\) 的相反数。 问能否通过这样的修改,使得 \(a\) 变为二分图排列?如果可以,请输出合法的修改方案,如果有多种方 案,请输出字典序最小的。

Solution

显然,如果要成为一个二分图排列,则最多只能拆分成两个上升子序列。

用 dp 乱搞就行了,对于每个 \(a_i\) 看要不要变成相反数。

Code

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

const int maxn = 1e6 + 5;
const int inf = 0x3f3f3f3f;

int n;
int a[maxn];
int f[maxn << 1];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i ++)
        cin >> a[i];
    for (int i = 1; i < n; i ++)
    {
        f[i] = -inf;
        f[i + n] = -inf;
    }
    f[n] = inf;
    f[n + n] = inf;
    for (int i = n; i > 1; i --)
    {
        if (a[i - 1] < f[i])
            f[i - 1 + n] = max(f[i - 1 + n], -a[i]);
        if (a[i - 1] < -a[i])
            f[i - 1 + n] = max(f[i - 1 + n], f[i]);
        if (a[i - 1] < f[i + n])
            f[i - 1 + n] = max(f[i - 1 + n], a[i]);
        if (a[i - 1] < a[i])
            f[i - 1 + n] = max(f[i - 1 + n], f[i + n]);
        if (-a[i - 1] < f[i])
            f[i - 1] = max(f[i - 1], -a[i]);
        if (-a[i - 1] < -a[i])
            f[i - 1] = max(f[i - 1], f[i]);
        if (-a[i - 1] < f[i + n])
            f[i - 1] = max(f[i - 1], a[i]);
        if (-a[i - 1] < a[i])
            f[i - 1] = max(f[i - 1], f[i + n]);
    }
    // for (int i = 1; i <= n * 2; i ++)
    //     cout << f[i] << " :f ";
    // cout << endl;
    // for (int i = 1; i <= n; i ++)
    //     cout << a[i] << " :a ";
    // cout << endl;
    if (f[1] == -inf && f[1 + n] == -inf)
    {
        cout << "NO\n";
        return ;
    }
    cout << "Yes\n";
    int x = -inf, y = -inf;
    for (int i = 1; i <= n; i ++)
    {
        if (f[i] != -inf)
        {
            if (-a[i] > x && f[i] > y)
            {
                x = a[i] = -a[i];
                continue;
            }
            if (-a[i] > y && f[i] > x)
            {
                y = a[i] = -a[i];
                continue;
            }
        }
        if (f[i + n] != -inf)
        {
            if (a[i] > x && f[i + n] > y)
            {
                x = a[i];
                continue;
            }
            if (a[i] > y && f[i + n] > x)
            {
                y = a[i];
                continue;
            }
        }
        if (x < y)
            swap(x, y);
    }
    for (int i = 1; i <= n; i ++)
        cout << a[i] << " ";
    cout << "\n";
}

int main()
{
    freopen("bipartite.in", "r", stdin);
    freopen("bipartite.out", "w", stdout);
    int tt;
    cin >> tt;
    while (tt --)
        solve();
    return 0;
}
posted @ 2023-10-04 18:25  Clyfort  阅读(57)  评论(0编辑  收藏  举报