牛客
第一周7.17
K Subdivision
题意:给定一个n个点m条边的无向图 ,可以将其中任意一条边分裂成一条长度为任意的链(向边中插任意多个点),可以操作任意多次(也可以不操作)。问经过这样处理之后,从 1号节点出发,至多走k步最多可以到多少个节点。 1 ≤ \leq ≤ n ≤ \leq ≤ 102, 0 ≤ \leq ≤ k ≤ \leq ≤ 109.
解法:添加点无疑会添加到两个地方:
- 无用边上,即bfs时并有用到此边。
- 叶节点,对于没有无用边连接的叶节点,须在叶节点前进行分裂即可。
步骤:
- 对图进行一遍bfs遍历,对遍历到的点进行标记。
- 再对图进行一遍bfs遍历,将无用边连接的顶点可增加的点数增加到无用边上。
- 判断若某顶点为叶节点并且无无用边与其连接,则将在叶节点前进行分裂。
AC代码:
#include <iostream>
#include <queue>
#include <map>
#include <cstring>
using namespace std;
const int N = 100010, M = 400010, INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
map<PII, int> mp;
int h[N], ne[M], e[M], idx;
int dis[N], f[N];
long long ans = 0;
int n, m, k;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
void bfs()
{
// bfs遍历
queue<int> q;
q.push(1);
dis[1] = 0;
while(q.size())
{
int t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i])
{
int d = e[i];
if(dis[d] == INF)
{
dis[d] = dis[t] + 1;
q.push(d);
mp[{t, d}] = mp[{d, t}] = 1; // 标记已用边
f[d] = t; // 记录父节点
}
}
}
queue<int> qq;
q = qq;
q.push(1);
while(q.size())
{
bool is_leaf = true; // 判断是否为叶子节点
int t = q.front();
q.pop();
for(int i = h[t]; i != -1; i = ne[i])
{
int d = e[i];
if(d == f[t] || dis[d] > k)continue; // 避免原路返回造成死循环,距离超出不考虑
is_leaf = false; // 有其他节点,该节点不是叶子节点
if(mp[{t, d}] == 1) // 若该边为有用边
{
q.push(d);
}
else // 若改边为无用边
{
ans += k - dis[t];
// cout << "无用边 " << k - dis[t] << endl;
}
}
if(is_leaf) // 若该点为叶子节点
{
ans += k - dis[t];
//cout << "叶子节点 " << k - dis[t] << endl;
}
}
}
int main()
{
memset(h, -1, sizeof h); // 初始化
memset(dis, 0x3f, sizeof dis); // 初始化距离
cin >> n >> m >> k;
for(int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
bfs();
long long cnt = 0;
for(int i = 1; i <= n; i++)if(dis[i] <= k)cnt++;
if(cnt != 1)cout << ans + cnt << endl;
else cout << "1" << endl;
return 0;
}
J Roulette
题意:Walk Alone 初始有n块钱,如果每次投x元,有一半的概率输掉这x元,另一半概率赢得2 x元。现在 Walk Alone 采取下述策略投注:
- 如果上一把赢了,这一把投xi = 1元
- 如果上一把输了,这一把投xi = 2 xi-1 元。
问 Walk Alone 有多大概率拿到n+m元离开。 1 <= n, m <= 109。
Output the probability that Walk Alone successfully earns an extra m yuan modulo 998 244 353. It can be shown that the answer can always be expressed as an irreducible fraction x/y, where x and y are integers and y !≡ 0 (mod 998 244 353). Output such an integer a that 0 ≤ a < 998 244 353 and a · y ≡ x (mod 998 244 353).
思路:由题意分析可知若处于输输输输…输赢的状态,那么其状态就相当于赢了一次,因此总共需要进行m轮这种状态就可以赢得m元。当有x元时,最多可以连输log2(x + 1)次就会把钱输光(等比数列求和得出),否则就可以进入下一轮还有赢的机会,因此对于每轮赢的概率为 1 − 1 / ( 2 l o g 2 ( x + 1 ) ) 1 - 1/(2^{log~2~(x + 1)}) 1−1/(2log 2 (x+1)),总共需要赢m轮,即 ∏ i = n + 1 n + m ( 1 − 1 2 l o g 2 ( i ) ) \prod_{i=n+1}^{n + m} (1-{1 \over {2^{log_2(i)}}}) ∏i=n+1n+m(1−2log2(i)1),故只需要计算n+1到n+m胜的概率之积即为最终结果。但是因为 n , m < = 1 0 9 n,m <= 10^9 n,m<=109, 如果进行遍历肯定会超时,通过观察发现,当x在一定区间内时,k的取值相等。例 x = 3 , 4 , 5 , 6 x = 3, 4,5,6 x=3,4,5,6 时k均为2,即获胜的概率相等,因此我们可以取一段区间获胜的概率的n次方代替n次遍历相同的概率来缩减所需时间。
因为题目要求 a · y ≡ x (mod 998 244 353)
⇒ \Rightarrow ⇒a ≡ x / y (mod 998 244 353)
⇒ \Rightarrow ⇒ a ≡ ( 1 − 1 2 l o g 2 ( i ) ) (1-{1 \over {2^{log~2~(i)}}}) (1−2log 2 (i)1) (mod 998 244 353)
⇒ \Rightarrow ⇒ (1 - a) ≡ ( 1 2 l o g 2 ( i ) ) ({1 \over {2^{log~2~(i)}}}) (2log 2 (i)1)(mod 998 244 353)
有费马定理可得: 若P为质数并且P与x互质, 则 xP-1≡1(mod P) ⇒ \Rightarrow ⇒ x * xP-2≡1 ( 1 2 l o g 2 ( i ) ) ({1 \over {2^{log~2~(i)}}}) (2log 2 (i)1)(mod P) ⇒ \Rightarrow ⇒ x-1 = xP-2
⇒ \Rightarrow ⇒ (1 - a) ≡ ( 2 l o g 2 ( i ) ) P − 2 ({2^{log~2~(i)}})^{P-2} (2log 2 (i))P−2(mod 998 244 353)
⇒ \Rightarrow ⇒ a = 1 - ( 2 l o g 2 ( i ) ) P − 2 ({2^{log~2~(i)}})^{P-2} (2log 2 (i))P−2(mod 998 244 353)
AC代码:
#include <iostream>
#include <cmath>
using namespace std;
const int M = 998244353;
typedef long long LL;
int n, m;
LL qmi(LL x, LL k)
{
LL ans = 1;
while(k)
{
if(k & 1)ans = (ans * x) % M;
x = (x * x) % M;
k >>= 1;
}
return ans % M;
}
LL inv(LL x)
{
return qmi(x, M - 2);
}
int main()
{
cin >> n >> m;
LL ans = 1;
for(LL i = n + 1; i <= n + m; )
{
LL num = log2(i);
LL p = (1 - inv(qmi(2, num)) + M) % M; // 求当前区间的概率
LL r = min(n + m, (1 << (num + 1)) - 1); // 查询区间最右侧
ans = ans * qmi(p, r - i + 1) % M;
i = r + 1;
}
cout << ans << endl;
return 0;
}
H Matches
题意:给定两个长度为n的序列{a}和{b},现在可以选择其中一个序列交换其中的两个数字,问经过至多一次操作后最小的 ∑ i = 0 n ∣ a i − b i ∣ \sum_{i = 0}^{n}{|a_i - b_i|} ∑i=0n∣ai−bi∣。 1 ≤ n ≤ 2 ∗ 1 0 5 , 0 ≤ ∣ a i ∣ , ∣ b i ∣ ≤ 1 0 12 1 \leq n \leq 2 * 10^5 , 0\leq|a_i|, |b_i| \leq 10^{12} 1≤n≤2∗105,0≤∣ai∣,∣bi∣≤1012。
解法:考虑交换ai, aj ,不妨设 ai < aj,则交换前后的贡献差 ∣ a i − b i ∣ + ∣ a j − b j ∣ − ∣ a j − b i ∣ − ∣ a i − b j ∣ |a_i - b_i| + |a_j - b_j| - |a_j - b_i| - |a_i - b_j| ∣ai−bi∣+∣aj−bj∣−∣aj−bi∣−∣ai−bj∣ ,即最大化贡献差。
考虑bi, bj 与ai ,aj 的四种关系可为:
只有当序列为反序包络或反序相交时,才会产生贡献值,并且我们想让贡献值越大越好。
AC代码:
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int N = 1000010;
#define x first
#define y second
int a[N], b[N];
int n;
int main()
{
cin >> n;
for(int i = 0; i < n; i++)cin >> a[i];
for(int i = 0; i < n; i++)cin >> b[i];
LL sum = 0;
vector<pair<PII, int>> v;
for(int i = 0; i < n; i++)
{
if(a[i] <= b[i])v.push_back({{a[i], b[i]}, 0}); // 记录并标记,01将数据分为两类
else v.push_back({{b[i], a[i]}, 1});
sum += abs(a[i] - b[i]);
}
sort(v.begin(), v.end());
LL ans = sum;
int mx[2] = {(int)-2e9, (int)-2e9};
for(auto i: v)
{
int l = i.x.x, r = i.x.y, f = i.y;
int mxx = mx[f ^ 1]; // 查询另一类的最靠右的位置
if(l <= mxx) ans = min(ans, sum - 2LL * (min(r, mxx) - l)); // 若满足反序包络或反序相交,则更新ans值
mx[f] = max(mx[f], r); // 更新最靠右的位置
}
cout << ans << endl;
return 0;
}
M Water
题意:给两个容积分别为A, B的水杯,每次可以执行以下的四种操作之一:
- 把其中一个水杯装满水。
- 把其中一个水杯中的全部水倒掉。
- 把其中一个水杯中现有的水全部喝完。
- 把一个杯子中的水尽可能转移到另一个水杯中,水不溢出。
恰好喝掉x体积的水,问最少要操作几次。 T组询问, 1 ≤ T ≤ 1 0 5 , 1 ≤ A , B ≤ 1 0 9 1 \leq T \leq 10^5, 1\leq A,B\leq 10^9 1≤T≤105,1≤A,B≤109。
记总共喝掉了x杯A水,y杯B水,可得:x * A + y * B = x (x, y不可能同时小于0),我们假设A的容量不小于B的容量,每次操作若y为负数,则需要将B中的水倒掉。
- 若x, y均为整数表示执行了x次: 倒满A ⇒ \Rightarrow ⇒喝掉A, 执行了y次: 倒满B ⇒ \Rightarrow ⇒喝掉B,共计2(x + y)次操作。
- 若x, y不全为整数,假设x > 0 && y < 0, 表示执行了x次倒满A,其中执行了y次将A倒给B ⇒ \Rightarrow ⇒倒掉B,但最后一次不需要倒掉B, 对于A要么喝掉,要么倒给B,所以共计2 * abs(x - y) - 1次操作。
AC代码
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if(!b)
{
x = 1, y = 0;
return a;
}
LL tmp = exgcd(b, a % b, y, x);
y -= a / b * x;
return tmp;
}
int main()
{
int T;
cin >> T;
while(T--)
{
LL a, b, x, y, m;
cin >> a >> b >> m;
LL d = exgcd(a, b, x, y); // 采用扩展欧几里得算法求出x和y的值
if(m % d) // 判断m是否可以由a,b进行表示
{
cout << "-1" << endl;
continue;
}
x *= m / d;
y *= m / d;
LL t1 = b / d;
LL t2 = a / d;
LL x1 = x, y1 = y;
x1 = (x1 % t1 + t1) % t1; // 划定初始解位于(0~t1)之间
LL k = (x1 - x) / t1; // 计算x增加了几个周期
y1 -= t2 * k; // y相应的减去几个周期
LL ans = 1e18;
for(int i = -10; i <= 10; i++)
{
LL x2 = x1 + i * t1;
LL y2 = y1 - i * t2;
// 在附近进行循环,判断最小值
if(x2 >= 0 && y2 >= 0)ans = min(ans, 2 * (x2 + y2));
else if(x2 >= 0 || y2 >= 0)ans = min(ans, 2*abs(x2 - y2) - 1);
}
cout << ans << endl;
}
return 0;
}