训练补题题解

2021/12/19 训练补题题解

1. Fight Against Traffic

  • 题目链接:CF954D_Fight Against Traffic

  • 知识点:最短路

  • 题目:无权简单图中加一条边避免起点 s s s 到终点 d d d 的最短路缩短,问合法方案数量。

  • 思路:若在点 a , b a,b a,b 之间加一条边 g ′ g' g 可使最短路缩短,那更新的最短路一定是经过 g ′ g' g 边的。于是可以算出所有点到起点终点两点 s , d s,d s,d 的最短路( d i j k s t r a dijkstra dijkstra),遍历尝试添加所有可加边,检查是否是合法的(min(startdist[i], startdist[j]) + 1 + min(enddist[i], enddist[j]))。

    #include<bits/stdc++.h>
    #define rep(i, a, b) for(int i = a; i <= b; i++)
    #define per(i, a, b) for(int i = a; i >= b; i--)
    const int INF = 0x7fffffff;
    #define ll long long
    #define PII pair<int, int>
    const int N =1e3 + 5;
    using namespace std;
    int n, s, d, m, k;
    ll g[N][N], ans;
    ll vis[N], startdist[N], enddist[N];
    vector<PII> vec[N];
    void init(){
        memset(g, 0, sizeof(g));
        rep(i, 1, n){
            vec[i].clear();
            startdist[i] = enddist[i] = INF;
        }
    }
    void dijkstra(int s, ll dist[]){
        memset(vis, 0, sizeof(vis));
        priority_queue<PII, vector<PII>, greater<PII> > heap;
        dist[s] = 0, heap.push({0, s});
        while(heap.size()){
            PII now = heap.top(); heap.pop();
            int nod = now.second;
            if(vis[nod]) continue;
            vis[nod] = 1;
            for(auto j : vec[nod]){
                int To = j.first, distmp = j.second;
                if(dist[To] > now.first + distmp){
                    dist[To] = now.first + distmp;
                    heap.push({dist[To], To});
                }
            }
        }
        return;
    }
    int main(){
        cin >> n >> m >> s >> d;
        init();
        rep(i, 1, m){
            int u, v; cin >> u >> v;
            g[u][v] = g[v][u] = 1;
            vec[u].push_back({v, 1}), vec[v].push_back({u, 1});
        }
        dijkstra(s, startdist);
        dijkstra(d, enddist);
        ans = 0;
        rep(i, 1, n) rep(j, i + 1, n){
            if(g[i][j]) continue;
            if(min(startdist[i], startdist[j]) + 1 + min(enddist[i], enddist[j]) >= startdist[d]) ans++;
        }
        cout << ans << endl;
    }
    /*
    5 4 1 5
    1 2
    2 3
    3 4
    4 5
    */
    

3. New Year and Rating

  • 题目链接:CF750C_New Year and Rating

  • 知识点:贪心

  • 思路:我们发现参赛者每多参加一场比赛,都会在参赛前无形中添加一条其等级的约束(参加 A A A 组比赛说明其等级不得小于 1900 1900 1900;参加 B B B 组比赛说明其等级不得大于 1899 1899 1899

    • 于是我们通过每场比赛不断精确其等级范围即可,注意当范围确定不满足参赛要求时,说明情况无解。
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x7fffffff;
    const int N = 1e5 + 5;
    const db eps = 1e-10;
    int n, c, d, have2;
    ll minn, maxn;
    int main(){
        cin >> n;
        minn = -INF, maxn = INF, have2 = 0;
        rep(i, 1, n){
            cin >> c >> d;
            if(d == 1){
                if(maxn < 1900){
                    puts("Impossible");
                    return 0;
                }
                if(minn < 1900) minn = 1900;  //更新
                minn += c, maxn += c;
            }
            else if(d == 2){
                have2 = 1;
                if(minn >= 1900){
                    puts("Impossible");
                    return 0;
                }
                if(maxn > 1899) maxn = 1899;  //更新
                maxn += c, minn += c;
            }
        }
        if(!have2) puts("Infinity");
        else cout << maxn << endl;
    }
    /*
    3
    -7 1
    5 2
    8 2
    */
    

4. Sasha and a Bit of Relax

  • 题目链接:CF1113C_Sasha and a Bit of Relax

  • 思路:假设一个区间 [ a x , a y ] [a_x,a_y] [ax,ay] 满足要求,则按照要求有
    a x ⊕ a x + 1 ⊕ . . . ⊕ a y = ( a x ⊕ . . . ⊕ a m i d ) ⊕ ( a m i d + 1 ⊕ . . . ⊕ a y ) = x ⊕ x = 0 a_x\oplus a_{x+1}\oplus ...\oplus a_y=(a_x\oplus ...\oplus a_{mid})\oplus (a_{mid+1}\oplus ...\oplus a_y)=x\oplus x=0 axax+1...ay=(ax...amid)(amid+1...ay)=xx=0
    于是若 y − x + 1 y-x+1 yx+1 为偶数且 a x ⊕ a x + 1 ⊕ . . . ⊕ a y = 0 a_x\oplus a_{x+1}\oplus ...\oplus a_y=0 axax+1...ay=0 ,则其此区间一定满足要求。

    证明:可以按位拆开考虑,当一位被区间内所有元素异或后为 0 0 0,表示一定有偶数个元素在这一位的位置为 1 1 1。将这偶数个元素分成两两一对,那每一对 1 1 1 可以共同在区间的前后两部分,或者分别在区间的前后两部分。这两种分发都导致这一对 1 1 1 使得区间两部分的异或和在这一位贡献相同,故前后两部分元素的异或和一定相同。

    • 于是计算有多少个长度为偶数的区间异或和为 0 0 0 即可。由于 n n n 太大,不可能用前缀异或和来 O ( n 2 ) O(n^2) O(n2) 的计算,于是可以将在奇/偶数位的相同前缀异或值存在一起,存在同一部分中的位置之间的区间均满足条件。注意 VI Xor[3][1 << 21] 不要开小了。
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x7fffffff;
    const int N = 3e5 + 5;
    const int NN = (1 << 21);
    const db eps = 1e-10;
    int n, a[N], sum[N], vis[NN];
    ll ans;
    VI Xor[3][NN];
    int main(){
        scanf("%d", &n);
        memset(vis, 0, sizeof(vis));
        rep(i, 1, n){
            scanf("%d", &a[i]);
            sum[i] = (i == 1 ? a[i] : sum[i - 1] ^ a[i]);
            Xor[(i % 2) ? 1 : 0][sum[i]].push_back(i);
            vis[sum[i]] = 1;
        }
        Xor[0][0].push_back(0);
        ans = 0;
        rep(i, 0, NN - 1){
            if(!vis[i]) continue;
            rep(k, 0, 1){
                VI tmp = Xor[k][i];
                if(!tmp.size()) continue;
                ans += (ll)tmp.size() * ((ll)tmp.size() - 1) / 2;  //直接算
            }
        }
        cout << ans << endl;
    }
    /*
    5
    1 2 3 4 5
    */
    

5. Select Half

  • 题目链接:ABC162F_Select Half

  • 知识点:动态规划

  • 思路:分为奇偶两种情况讨论。设 f [ i ] f[i] f[i] 表示前 i i i 个数字中选择 ⌊ i 2 ⌋ \lfloor\dfrac{i}{2}\rfloor 2i 个两两不相邻数字的最大可能和,来线性动态规划求 f [ n ] f[n] f[n]

    • 首先 f [ 1 ] = 0 f[1]=0 f[1]=0 因为只能选 0 0 0 个数。正常来说,计算 f [ i ] f[i] f[i] 只需要考虑选或不选 a [ i ] a[i] a[i] 即可。
    • 但当 i i i 为偶数时,若不选 a [ i ] a[i] a[i] 则无论最大可能和一定要选 1 , 3 , 5 , . . . , i − 1 1,3,5,...,i-1 1,3,5,...,i1 等数字,于是有 f[i] = max(f[i - 2] + a[i], sum[i - 1]) .
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x7fffffff;
    const int N = 2e5 + 5;
    const db eps = 1e-10;
    ll n, a[N], sum[N], f[N];
    int main(){
        cin >> n;
        rep(i, 1, n){
            cin >> a[i];
            sum[i] = a[i] + (i > 2 ? sum[i - 2] : 0);
        }
        f[1] = 0;  //不能写 max(0ll, a[1]), 因为 1 / 2 = 0
        rep(i, 2, n){
            if(i % 2) f[i] = max(f[i - 2] + a[i], f[i - 1]);
            else f[i] = max(f[i - 2] + a[i], sum[i - 1]);  //偶数时一定要考虑 sum[i - 1] 的情况
        }
        cout << f[n] << endl;
    }
    /*
    27
    18 -28 18 28 -45 90 -45 23 -53 60 28 -74 -71 35  -26 -62 49
    -77 57 24 -70 -93 69 -99 59 57 -49
    */
    

6. Equals

  • 题目链接:ABC097D_Equals

  • 知识点:并查集

  • 思路:重点可以任意(无限)次交换操作,(自己试试)发现若一堆位置互相相连(可交换),则每个数字一定可以换到同一堆的任意一个位置,则若 a [ i ] a[i] a[i] i i i 相连,则 a [ i ] a[i] a[i] 一定可以换到 a [ i ] a[i] a[i] 对应数值的位置。

    • 于是在一个集合中便可以换到对应位置,用并查集将所有交换对划到集合中,最后检查对应数对即可。
    //可以任意次交换,于是在一团中的一定可以换到想去的位置
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    #define PII pair<int, int>
    const db Pi = 3.141592653589793;
    const int INF = 0x7fffffff;
    const int N = 1e5 + 5;
    const db eps = 1e-10;
    int n, m, a[N], pre[N];
    ll ans = 0;
    int find(int x){
        if(pre[x] == x) return x;    
        return pre[x] = find(pre[x]);
    }
    void join(int x,int y){
        int fx = find(x), fy = find(y); 
        if(fx != fy) pre[fx] = fy;
    }
    int main(){
        cin >> n >> m;
        rep(i, 1, n){
            cin >> a[i];
            pre[i] = i;
        }
        rep(i, 1, m){
            int x, y; cin >> x >> y;
            join(x, y);
        }
        rep(i, 1, n) if(find(i) == find(a[i])) ans++;
        cout << ans << endl;
    }
    /*
    5 2
    5 3 1 4 2
    1 3
    5 4
    */
    

8. Trailing Loves (or L’oeufs?)

  • 题目链接:1114C_Trailing Loves (or L’oeufs?)

  • 知识点:数学(素数)

  • 题目:求十进制 n n n 的阶乘 n ! n! n! m m m 进制表示法后面有多少零。

  • 思路 n 10 ! n_{10}! n10! m m m 进制下有 t t t 个零表示 m t ∣ n !    且    m t + 1 ∤ n ! \color{red}m^t|n! \;且\; m^{t+1}\not |n! mtn!mt+1n!

    • 于是将 m m m 质因数分解 m = p 1 c 1 p 2 c 2 p 3 c 3 . . . m=p_1^{c_1}p_2^{c_2}p_3^{c_3}... m=p1c1p2c2p3c3...,后我们对于每个质因数 p i p_i pi 计算 r e a l i = n ! / p i real_i=n!/p_i reali=n!/pi,故 t i = r e a l i c i \color{red}t_i=\dfrac{real_i}{c_i} ti=cireali,对于不同质因数 p i p_i pi,最终的 t t t 一定是 min ⁡ { t i } \min\{t_i\} min{ti}
    • 如何 O ( log ⁡ n ) O(\log n) O(logn) 计算 r e a l i = n ! / p i real_i=n!/p_i reali=n!/pi:发现 n ! n! n! 中质数因子 p p p 的个数就是 1 ∼ n 1\sim n 1n 中每个数含有的质因数 p p p 个数之和。那么我们发现 1 ∼ n 1\sim n 1n 中, p p p 的倍数,即至少有一个质因子 p p p 的数显然有 ⌊ n p ⌋ \lfloor\dfrac{n}{p} \rfloor pn 个,而 p 2 p^2 p2 的倍数,即至少有两个质因子 p p p 的数显然是有 ⌊ n p 2 ⌋ \lfloor\dfrac{n}{p^2} \rfloor p2n 不过由于这两个质因子 p p p 中有一个已经在 ⌊ n p ⌋ \lfloor\dfrac{n}{p} \rfloor pn 统计过了,所以只需要再统计第二个质因子,也就是直接累加 ⌊ n p 2 ⌋ \lfloor\dfrac{n}{p^2} \rfloor p2n 而不是累乘。即 ⌊ n p ⌋ + ⌊ n p 2 ⌋ + . . . + ⌊ n p log ⁡ p n ⌋ = ∑ p k ≤ n ⌊ n p k ⌋ \color{red}\lfloor\dfrac{n}{p} \rfloor+\lfloor\dfrac{n}{p^2} \rfloor+...+\lfloor\dfrac{n}{p^{\log_p n}} \rfloor=\sum\limits_{p^k\le n}\lfloor\dfrac{n}{p^k} \rfloor pn+p2n+...+plogpnn=pknpkn
    • 因为 m ≤ 1 e 12 m\le 1e12 m1e12 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 ∗ 23 ∗ 29 ∗ 31 ∗ 37 = 7 , 420 , 738 , 134 , 810 > m 2*3*5*7*11*13*17*19*23*29*31*37=7,420,738,134,810>m 23571113171923293137=7,420,738,134,810>m,故 m m m 最多有 11 11 11 个不同质因子,复杂度不会太高。
    #include<bits/stdc++.h>
    using namespace std;
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    #define ll long long
    #define db double
    #define VI vector<int>
    const int N = 1e5 + 5;
    ll n, m, cnt = 0, ans, tmp;
    struct AC{
        ll p, num, real;
    }a[N];
    int main(){
        cin >> n >> m;
        ll y = m;
        rep(i, 2, sqrt(m)){
            if(y % i == 0){
                tmp = 0;
                while(y % i == 0) y /= i, tmp++;
                a[++cnt].p = i, a[cnt].num = tmp;
            }
        }
        if(y != 1) a[++cnt].p = y, a[cnt].num = 1;
        rep(i, 1, cnt){
            tmp = 0;
            for(ll j = n; j; j /= a[i].p) tmp += j / a[i].p;  //log(n) 计算 n! 中有多少个 a[i].p
            a[i].real = tmp; 
        }
        ans = (1ll << 62);
        rep(i, 1, cnt) ans = min(ans, a[i].real / a[i].num);
        cout << ans << endl;
    }
    /*
    6 9
    */
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值