『模拟赛题解』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;
}