多校训练(补题)

牛客

第一周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 采取下述策略投注:

  1. 如果上一把赢了,这一把投xi = 1元
  2. 如果上一把输了,这一把投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)}) 11/(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(12log2(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)}}}) (12log 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))P2(mod 998 244 353)

⇒ \Rightarrow a = 1 - ( 2 l o g   2   ( i ) ) P − 2 ({2^{log~2~(i)}})^{P-2} (2log 2 (i))P2(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=0naibi 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} 1n2105,0ai,bi1012

解法:考虑交换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| aibi+ajbjajbiaibj ,即最大化贡献差。

考虑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的水杯,每次可以执行以下的四种操作之一:

  1. 把其中一个水杯装满水。
  2. 把其中一个水杯中的全部水倒掉。
  3. 把其中一个水杯中现有的水全部喝完。
  4. 把一个杯子中的水尽可能转移到另一个水杯中,水不溢出。

恰好喝掉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 1T105,1A,B109

记总共喝掉了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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值