AtCoder Beginner Contest 227

C - ABC conjecture

题意:

给定一个 N N N,现在问有多少个三元组 ( A , B , C ) (A,B,C) (A,B,C), A ≤ B ≤ C A≤B≤C ABC A B C ≤ N ABC≤N ABCN

思路:

直接暴力枚举,时间复杂度 O ( N 2 3 ) O(N^{\frac{2}{3}}) O(N32),枚举A和B然后算出有多少个C

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int s[N];
#define ll long long
int main() {
    ll n;
    cin >> n;
    ll res = 0;
    for (ll i = 1; i * i * i <= n; i++) {
        for (ll j = i; i * j * j <= n; j++) {
            res += n / i / j - j + 1;
        }
    }
    cout << res << endl;
}

D - Project Planning.

题意:

给一个长度为 N N N的数列, A 1 . . . . A N A_1....A_N A1....AN, A i ≥ 1 A_i≥1 Ai1,现在每次选 K K K个数减去 1 1 1,每个数只能被减到 0 0 0,问整个数列最多能操作几次

思路:

假设可以操作 P P P次,现在令 s u m = sum= sum= ∑ i = 1 n m i n ( P , A i ) \sum_{i=1}^{n} min(P,A_i)\qquad i=1nmin(P,Ai),
如果 P × K > s u m P×K>sum P×K>sum,那么很明显是不够减的,所以一定不成立
可以假设当 P × K ≤ s u m P×K≤sum P×Ksum时,我们可以操作 P P P
每次操作的策略始终都是拿最大的 K K K个数
假设 ≥ P ≥P P的个数为 n u m num num
1. 如果 n u m ≥ K num≥K numK,那么此时一定是满足 P × K > s u m P×K>sum P×K>sum
2. 如果 n u m = K − 1 num=K-1 num=K1,那么为了保证不等式剩下的数的和一定 ≥ P ≥P P,又由于大小 < P

<P,所以个数一定 ≥ 2 ≥2 2 且最大是 P − 1 P-1 P1,每次拿前 K − 1 K-1 K1大的数,剩下的一个数从小数中拿,由于剩下的数的和一定 ≥ P ≥P P,所以最后拿完 P P P次后,拿出来的数的和一定满足 P × K > s u m P×K>sum P×K>sum
3. 如果 n u m = K − 2 num=K-2 num=K2,那么为了保证不等式剩下的数的和一定 ≥ P ≥P P
又由于大小 < P

<P,所以个数一定 ≥ 3 ≥3 3,且最大是 P − 1 P-1 P1,每次拿前 K − 2 K-2 K2大的数,剩下的一个数从小数中拿,第一次拿一定成功,拿完后,开始第二次拿,此时不等式就是 s u m − k ≥ P ∗ K − K sum-k≥P*K-K sumkPKK,如果第二次拿失败了,说明剩下的 3 3 3个数中,有至少2个数是 0 0 0,这就说明之前有两个数是 1 1 1,且这两个数是剩下的数中最大的两个,那么就说明剩下的三个数是最大情况就是 1,1,0,由于而这与中间推导的个数一定 > = 3 >=3 >=3,所以第二次拿一定是可以的,…一直推导到可以拿 P P P

K-1. 如果 n u m = 0 num=0 num=0,…个数一定 ≥ K − 1 ≥K-1 K1,…按照上面归纳总结即可
最后按照不等式 P × K ≤ s u m P×K≤sum P×Ksum,来二分即可
洛谷也有别的结论及证明 link.

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
#define ll long long
ll a[N];
int n, k;
bool check(ll x) {
    ll sum = 0;
    ll cnt = 0;
    for (int i = 1; i <= n; i++) {
        sum += min(x, a[i]);
    }
    return sum >= x * k;
}
int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    ll l = 0, r = 1e18 / k;
    while (l < r) {
        ll mid = (l + r + 1) / 2;
        if (check(mid)) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    cout << l << endl;
}

E - Swap

题意:

给定一个只有 K , E , Y K,E,Y K,E,Y字母的一个字符串,现在问最多能交换相邻字符串 K K K次,问能形成多少种字符串

思路:

原字符串定义为 A A A,变换后的字符串定义为 B B B
定义 d p ( i , e , y , x ) dp(i,e,y,x) dp(i,e,y,x) B B B字符串前 i i i位有 e 个 E e个E eE y 个 Y y个Y yY 需要操作 x x x次才能变成与 A A A字符串前 i − e − y 个 K i-e-y个K ieyK e 个 E e个E eE y 个 Y y个Y yY的相对顺序一致
最终就是 d p ( l e n , e c n t , y c n t , i ) dp(len,ecnt,ycnt,i) dp(len,ecnt,ycnt,i)从0到k的累加
那么此时已知 d p ( i , e , y , x ) dp(i,e,y,x) dp(i,e,y,x)的话,看对 d p ( i + 1 ) dp(i+1) dp(i+1)产生什么影响即可
AtCoder Beginner Contest 227_第1张图片

在当前状态下,如果末尾来了一个 K K K,这是第 i − e − y i-e-y iey K K K为了保证相对位置一样,就需要新来的 K K K与原序列的第 i − e − y i-e-y iey K K K连线,且保证与其他的线条不相交,此时那样的话就需要把当前这个 K K K往左移动几位,需要的步数,就是操作数,需要的步数就是,把 K K K到对应位置,需要跨越的 E 和 Y E和Y EY的个数,假设是 s s s
那么此时 d p ( i + 1 , e , y , x + s ) + = d p ( i , e , y , x ) dp(i+1,e,y,x+s)+=dp(i,e,y,x) dp(i+1,e,y,x+s)+=dp(i,e,y,x)
同理讨论 Y Y Y E E E即可
d p ( i + 1 , e + 1 , y , x + s ) + = d p ( i , e , y , x ) dp(i+1,e+1,y,x+s)+=dp(i,e,y,x) dp(i+1,e+1,y,x+s)+=dp(i,e,y,x)
d p ( i + 1 , e , y + 1 , x + s ) + = d p ( i , e , y , x ) dp(i+1,e,y+1,x+s)+=dp(i,e,y,x) dp(i+1,e,y+1,x+s)+=dp(i,e,y,x)

#include <bits/stdc++.h>
using namespace std;
const int N = 33, M = N * N;
#define ll long long
string s;
int k;
ll dp[N][N][N][M];

int main() {
    cin >> s >> k;
    int len = s.size();
    k = min(k, len * (len - 1) / 2);

    vector<int> kpos, epos, ypos;
    for (int i = 0; i < len; i++) {
        if (s[i] == 'K') {
            kpos.push_back(i);
        }
        if (s[i] == 'E') {
            epos.push_back(i);
        }
        if (s[i] == 'Y') {
            ypos.push_back(i);
        }
    }

    int kcnt = kpos.size(), ecnt = epos.size(), ycnt = ypos.size();
    dp[0][0][0][0] = 1;

    for (int i = 0; i < len; i++) {
        for (int e = 0; e <= ecnt; e++) {
            for (int y = 0; y <= ycnt; y++) {
                int kk = i - e - y;
                //非法情况
                if (kk < 0 || kk > kcnt) continue;

                for (int sw = 0; sw <= k; sw++) {
                    //无贡献,直接跳过,省时间
                    if (!dp[i][e][y][sw]) continue;

                    if (kk < kcnt) {
                        int step = 0;
                        for (int now = 0; now < e; now++) {
                            if (epos[now] > kpos[kk]) {
                                step++;
                            }
                        }
                        for (int now = 0; now < y; now++) {
                            if (ypos[now] > kpos[kk]) {
                                step++;
                            }
                        }
                        if (sw + step <= k) {
                            dp[i + 1][e][y][sw + step] += dp[i][e][y][sw];
                        }
                    }

                    if (e < ecnt) {
                        int step = 0;
                        for (int now = 0; now < kk; now++) {
                            if (kpos[now] > epos[e]) {
                                step++;
                            }
                        }
                        for (int now = 0; now < y; now++) {
                            if (ypos[now] > epos[e]) {
                                step++;
                            }
                        }
                        if (sw + step <= k) {
                            dp[i + 1][e + 1][y][sw + step] += dp[i][e][y][sw];
                        }
                    }
                    if (y < ycnt) {
                        int step = 0;
                        for (int now = 0; now < kk; now++) {
                            if (kpos[now] > ypos[y]) {
                                step++;
                            }
                        }
                        for (int now = 0; now < e; now++) {
                            if (epos[now] > ypos[y]) {
                                step++;
                            }
                        }
                        dp[i + 1][e][y + 1][sw + step] += dp[i][e][y][sw];
                    }
                }
            }
        }
    }
    ll res = 0;
    for (int i = 0; i <= k; i++) {
        res += dp[len][ecnt][ycnt][i];
    }
    cout << res << endl;
}

F - Treasure Hunting

题意:

n × m n×m n×m的矩阵,从 ( 1 , 1 ) (1,1) (1,1)点走到 ( n , m ) (n,m) (n,m)点,只可以往下走或者往右走,从起点走到终点的路径中,令 s u m = sum= sum=走的路径中 n + m − 1 n+m-1 n+m1个数中,最大的 K K K个数的和,问这个和最小是多少

思路:

由于 n 和 m n和m nm很小, n × m n×m n×m最大是900,所以第 K K K大的数字,最多有 900 900 900种情况,所以可以枚举第 K K K大的数字为 n o w now now,然后跑 d p dp dp
d p ( i , j , k ) dp(i,j,k) dp(i,j,k)为从起点到 ( j , k ) (j,k) (j,k),最大的 i i i个数的和
如果下一个点可达,并且下一个点的权值满足 ≥ n o w ≥now now,那就是
d p ( i + 1 , j + 1 , k ) = m i n ( d p ( i + 1 , j + 1 , k ) , d p ( i , j , k ) + a ( j + 1 , k ) ) dp(i+1,j+1,k)=min(dp(i+1,j+1,k),dp(i,j,k)+a(j+1,k)) dp(i+1,j+1,k)=min(dp(i+1,j+1,k),dp(i,j,k)+a(j+1,k))
d p ( i + 1 , j , k + 1 ) = m i n ( d p ( i + 1 , j , k + 1 ) , d p ( i , j , k ) + a ( j , k + 1 ) ) dp(i+1,j,k+1)=min(dp(i+1,j,k+1),dp(i,j,k)+a(j,k+1)) dp(i+1,j,k+1)=min(dp(i+1,j,k+1),dp(i,j,k)+a(j,k+1))
如果下一个点的权值 ≤ n o w ≤now now
那么就是
d p ( i , j + 1 , k ) = m i n ( d p ( i , j + 1 , k ) , d p ( i , j , k ) ) dp(i,j+1,k)=min(dp(i,j+1,k),dp(i,j,k)) dp(i,j+1,k)=min(dp(i,j+1,k),dp(i,j,k))
d p ( i , j , k + 1 ) = m i n ( d p ( i , j , k + 1 ) , d p ( i , j , k ) ) dp(i,j,k+1)=min(dp(i,j,k+1),dp(i,j,k)) dp(i,j,k+1)=min(dp(i,j,k+1),dp(i,j,k))
最后取 d p ( k , n , m ) dp(k,n,m) dp(k,n,m)即可

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf = 1e18;
const int N = 33;
int n, m, k;
int g[N][N];
ll dp[N][N][N + N];
int main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> g[i][j];
        }
    }

    ll res = inf;

    for (int x = 1; x <= n; x++) {
        for (int y = 1; y <= m; y++) {
            int now = g[x][y];
            memset(dp, 0x3f, sizeof(dp));
            if (g[1][1] >= now) {
                dp[1][1][1] = g[1][1];
            }
            if (g[1][1] <= now) {
                dp[0][1][1] = 0;
            }

            for (int i = 0; i <= k; i++) {
                for (int j = 1; j <= n; j++) {
                    for (int t = 1; t <= m; t++) {
                        if (j != n) {
                            if (i != k && g[j + 1][t] >= now) {
                                dp[i + 1][j + 1][t] =
                                    min(dp[i + 1][j + 1][t],
                                        dp[i][j][k] + g[j + 1][t]);
                            }
                            if (g[j + 1][t] <= now) {
                                dp[i][j + 1][t] =
                                    min(dp[i][j + 1][t], dp[i][j][t]);
                            }
                        }
                        if (t != m) {
                            if (i != k && g[j][t + 1] >= now) {
                                dp[i + 1][j][t + 1] =
                                    min(dp[i + 1][j][t + 1],
                                        dp[i][j][t] + g[j][t + 1]);
                            }
                            if (g[j][t + 1] <= now) {
                                dp[i][j][t + 1] =
                                    min(dp[i][j][t + 1], dp[i][j][t]);
                            }
                        }
                    }
                }
            }
     
            res = min(res, dp[k][n][m]);
        }
    }
    cout << res << endl;
}

G - Divisors of Binomial Coefficient

题意:

让你求 C N K C_{N}^{K} CNK的因子个数, 0 ≤ N ≤ 1 0 12 , 0 ≤ K ≤ m i n ( 1 0 6 , N ) 0≤N≤10^{12},0≤K≤min(10^6,N) 0N1012,0Kmin(106,N)

思路:

由于数据范围很大,所有不可能把数算出来再进行质因数分解。把组合数展开, C N K = N ( N − 1 ) . . . . . . ( N − K + 1 ) 1 ⋅ 2 ⋅ 3...... K C_{N}^{K}=\frac{N(N-1)......(N-K+1)}{1·2·3......K} CNK=123......KN(N1)......(NK+1)
对分子和分母同时进行质因数分解,由于 N 和 K N和K NK都很大,所以不能直接拿出分子分母的某个数进行质因数分解,可以发现 1 0 6 10^6 106以内的数,非常容易筛出素数,且分母的每个数在质因数分解后,最大的质因子一定在 1 0 6 10^6 106之内,而分子最大是 1 0 12 10^{12} 1012,质因数分解后,拥有的大于 1 0 6 10^6 106的素数一定只有 1 1 1个,且指数是 1 1 1,否则就超过 1 0 12 10^{12} 1012了。所以先筛出 1 0 6 10^6 106以内的素数,然后枚举每个素数,然后再枚举能整除对应素数的分子分母,然后算出其指数,最后筛完,看分子那些大数,是否有 1 0 6 10^6 106以上的素数即可。然后就是求约数之和的步骤了,时间复杂度大约是 O ( M l o g l o g M ) O(MloglogM) O(MloglogM)类似于埃氏筛, M = m i n ( N , K ) M=min(\sqrt{N},K) M=min(N ,K)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 10, mod = 998244353;
bool st[N];
int primes[N], cnt;
ll n, k;
void get_primes(int n) {
    for (int i = 2; i <= n; i++) {
        if (!st[i]) primes[cnt++] = i;
        for (int j = 0; primes[j] <= n / i; j++) {
            st[primes[j] * i] = 1;
            if (i % primes[j] == 0) {
                break;
            }
        }
    }
}

int main() {
    get_primes(N - 1);
    cin >> n >> k;

    vector<ll> deno(k + 1);
    for (ll i = 1; i <= k; i++) {
        deno[i] = i;
    }
    vector<ll> nums(k);
    ll now = n - k + 1;
    for (ll i = 0; i < k; i++) {
        nums[i] = now + i;
    }

    ll res = 1;

    for (int p = 2; p < N; p++) {
        if (st[p] == 0) {
            ll dex = 0;

            for (ll i = p; i <= k; i += p) {
                while (deno[i] % p == 0) {
                    dex--;
                    deno[i] /= p;
                }
            }

            for (ll i = (n - k + 1 + p - 1) / p * p; i <= n; i += p) {
                while (nums[i - now] % p == 0) {
                    dex++;
                    nums[i - now] /= p;
                }
            }
            res = res * (dex + 1) % mod;
        }
    }

    for (ll i = n - k + 1; i <= n; i++) {
        if (nums[i - now] != 1) {
            res = (ll)res * 2 % mod;
        }
    }
    cout << res << endl;
}

To be continued
如果你有任何建议或者批评和补充,请留言指出,不胜感激

你可能感兴趣的:(AtCoder,笔记,数论,组合数,动态规划)