【题解】2020牛客寒假算法基础集训营1 (全题解)

ProblemA: honoka和格点三角形

题目大意: 给你一个 n m n*m 的格点矩阵,请你找出”好三角形“的个数。

解题思路:

可以把面积为 1 1 的“好三角形”分为两类分开统计:两条边和两个坐标轴平行;只有一条边和某个坐标轴平行。

对于第一种情况,一定是 1 2 1∗2 或者 2 1 2∗1 的形式,一个 1 2 1∗2 的矩形中含有4个不同的三角形。总数是 4 ( ( n 2 ) ( m 1 ) + ( m 2 ) ( n 1 ) ) 4∗((n−2)∗(m−1)+(m−2)∗(n−1))

对于第二种情况,可以分别统计底边为 2 、高为 1 和底边为 1 、高为 2的情况。要注意底边靠近边界时的特殊讨论。

①对于底边为2,高为1的情况:

若底边和x轴平行,那么底边横向移动(指x轴水平移动,下同)有 n 2 n−2 种可能,“对点”(指底边相对的点)的某一面选择有 n 2 n−2 种可能(某一面选择,指的是底边固定的情况,对点在一条直线上移动所做出的选择),而底边纵向移动有 m m 种情况,其中有 ( m 2 ) (m-2) 种情况对点可以选择两个面,2种情况对点只能选择一个面(当底边移动到格点阵边界的时候)。

因此纵向移动折合为 2 ( m 2 ) + 2 2∗(m−2)+2 ,即 2 ( m 1 ) ( m 1 ) ( n 2 ) ( n 2 ) 2∗(m−1)(m-1)*(n-2)*(n-2)

若底边和y轴平行,同理可推出 2 ( n 1 ) ( m 2 ) ( m 2 ) 2∗(n−1)∗(m−2)∗(m−2)
②对于底边为1,高为2的情况,推理方法和上面类似。

示意图:
在这里插入图片描述
代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = 1e9 + 7;
int main()
{
    ll n, m;

    cin >> n >> m;

    ll s = (n - 2) * (m - 1) * 4 % mod + (n - 1) * (m - 2) * 4 % mod; // 两条边分别和x轴和y轴平行的部分
    //底部为2,高为1的情况
    s = (s + 2 * (n - 1) * (m - 2) % mod * (m - 2) % mod + 
         2 * (m - 1) * (n - 2) % mod * (n - 2) % mod) % mod;
    // 底部为1,高为2的情况
    s = (s + 2 * (n - 2) * (m - 1) % mod * (m - 2) % mod + 
         2 * (m - 2) * (n - 1) % mod * (n - 2) % mod) % mod;
    cout << s;
}

ProblemB: kotori和bangdream

题目大意: 签到题计算期望。

解题思路: 直接按照数学期望的定义计算即可。

代码:

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n, a, b, x;
    cin >> n >> x >> a >> b;
    int ans = a * x + b * (100 - x);
    printf("%.2f\n", ans * n / 100.0);
    return 0;
}

Problem C: umi和弓道

题目大意: 现在有一个人在某一个坐标点 ( x 0 , y 0 ) (x_0, y_0) 上,有 n n 个靶子,现在可以在 x x 轴或者 y y 轴上放置一块挡板,让这个人只能射中 k k 箭,请你求出这块挡板的最小长度。所有点都不在坐标轴上。

解题思路:

我们可以先确定umi所在位置的象限,因为同一象限靶子不可能被挡住。

然后把剩下的点和 ( x 0 , y 0 ) (x_0, y_0) 连起来,求出该直线与 x x 轴和 y y 轴的交点:

直线方程: k x + b = y kx + b = y

对于 y y 轴上的截距,因为 k = y 1 y 2 x 1 x 2 k=\frac{y_1-y_2}{x_1-x_2} ,那么截距 b b 的值随便代入一个点就能求出: b = y 0 k x 0 b = y0-kx_0

对于 x x 轴上的截距,我们可以将方程写为 k 1 y + b 1 = x k_1y + b_1 = x ,这个时候我们知道 k 1 = 1 k k_1 = \frac{1}{k} ,而 b 1 b_1 就是直线在 x x 轴上的截距,所以 b 1 = x 0 k 1 y 0 b_1 = x_0 - k_1y_0

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

vector<double> v1, v2; // v1、v2分别寸umi和靶子连接的线段与两个坐标轴的交点(如果存在的话)
int main()
{
    double x0, y0;
    int n, k;
    cin >> x0 >> y0 >> n >> k;
    k = n - k; // 一共要拦截n - k支箭
    for(int i = 0; i < n; ++i) {
        double x, y;
        cin >> x >> y;
        if(x * x0 < 0) { // 与y轴的交点
            v2.push_back(y0 - x0 * (y - y0) / (x - x0));
        }
        if(y * y0 < 0) { // 与x轴的交点
            v1.push_back(x0 - y0 * (x - x0) / (y - y0));
        }
    }
    double minv = 1e18;
    sort(v1.begin(), v1.end());
    sort(v2.begin(), v2.end());
    // 维护x轴挡住n - k个点的挡板长度最小值
    if(v1.size() >= k) {
        double head = 0, tail = k - 1;
        while(tail < v1.size()) {
            minv = min(minv, v1[tail] - v1[head]);
            tail++, head++;
        }
    }
    // 维护y轴挡住n-k个点的挡板的长度最小值
    if(v2.size() >= k) {
        double head = 0, tail = k - 1;
        while(tail < v2.size()) {
            minv = min(minv, v2[tail] - v2[head]);
            tail++, head++;
        }
    }
    if(minv == 1e18) cout << -1 << endl;
    else cout << fixed << setprecision(7) << minv << endl;
    return 0;
}

Problem D: hanayo和米饭

题目大意: 有n个数,分别为 1 , 2 . . . , n 1,2,..., n ,现在给你其中 n 1 n-1 个数,问你剩下的一个数是什么。

解题思路: 使用等差数列求和公式求得n个数的和,然后减去这 n 1 n-1 个数即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    ll n, t;
    cin >> n;
    ll ans = (1 + n) * n / 2;
    for(int i = 0; i < n - 1; ++i) {
        cin >> t;
        ans -= t; 
    }
    cout << ans << endl;
    return 0;
}

Problem E: rin和快速迭代

题目大意: f ( x ) f(x) x x 的因子个数,不停的迭代,每次 x x 的初始值为上一次 f ( x ) f(x) 的值,问 f ( x ) f(x) 什么时候收敛到2。

解题思路: 根据质数分解定理,除1以外的任意数都能表示成若干个质数相乘, p = 2 n 3 p 5 q . . . p = 2^n * 3^p * 5^q ... ,我们根据质数的指数可以进行排列组合,得到 p p 的因数个数为 ( n + 1 ) ( p + 1 ) ( q + 1 ) . . . . (n+1)*(p+1)*(q+1).... ,因为这个收敛速度非常快,我们可以直接使用模拟的方法来做。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
using namespace std;
ll num(ll n)
{
    ll cnt;
    ll res = 1;
    for(ll i = 2; i <= n / i; i++){
        if(n % i == 0){
            cnt = 0;
            while(n % i == 0){
                n = n / i;
                ++cnt;
            }
            res *= (cnt + 1);
        }
    }
    if(n > 1) res *= 2;
    return res;
}
 
 
int main()
{
    ll n;
    ll cnt = 0;
    cin >> n;
    while(true) {
        ll tmp = num(n);
        cnt++;
        if(tmp == 2) break;
        n = tmp;
    }
    cout << cnt << endl;
    return 0;
}

Problem F: maki和tree

题目大意: 给你一棵树,树的每个结点上都一种颜色,可能为黑色 B B ,也可能为白色 W W ,问你任意两点之间只经过1个黑色点的路径有多少条?

解题思路: 经过一个黑点的路径有两种:两个端点都是白点;其中一个端点是黑点。
因此我们可以先用并查集预处理,将每个白点连通块上的白点个数统计出来。这样我们就可以得知每个黑点所连接的白点的权值(即连通块白点数)。
设某黑点连接了 k 个白点,第 i 个白点的权值为 f(i) 。

那么第一种路径的数量就是 i = 1 k j = i + 1 k f ( i ) f ( j ) \sum_{i=1}^{k}\sum_{j=i+1}^{k}f(i)*f(j) ,而第二种路径只要记录与黑点连接的白色连通块即可,如下图所示:

在这里插入图片描述

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 +10;
char s[N];
int fa[N], num[N]; //分别代表并查集数组和白色连通块中点的数量
vector<int> G[N];

int Find(int x) {
    return x == fa[x] ? x : fa[x] = Find(fa[x]);
}

void Union(int x, int y) {
    x = Find(x), y = Find(y);
    if(x == y) return;
    fa[x] = y, num[y] += num[x];
}

int main()
{
    int n, x, y;
    cin >> n;
    cin >> s + 1;
    for(int i = 1; i <= n; ++i) {
        fa[i] = i;
        num[i] = (s[i] == 'W'); // 1代表白色,0代表黑色
    }
    for(int i = 0; i < n - 1; ++i) {
        cin >> x >> y;
        G[x].push_back(y), G[y].push_back(x);
        /* 预处理白色连通块 */
        if(s[x] == 'W' && s[y] == 'W') Union(x, y); 
    }
    ll ans = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == 'W') continue;
        ll tmp = 0;
        for(int j = 0; j < G[i].size(); ++j)
            tmp += num[Find(G[i][j])];
        ans += tmp; //黑色为顶点的情况
        //白色为端点的情况
        for(int j = 0; j < G[i].size(); ++j) {
            tmp -= num[Find(G[i][j])];
            ans += tmp * num[Find(G[i][j])];
        }
    }
    cout << ans << endl;
    return 0;
}

还可以使用dfs或者bfs来处理白色连通块:

bfs:

#include<bits/stdc++.h>
using namespace std;
 
const int N = 100010;
vector<int> ve[N];
vector<int> v;
char c[N];
 
long long sum = 0;
long long dis[N];

/* 统计点x所在的连通块的点的个数 */
int bfs(int x)
{
    v.clear();
    queue<int> q;
    q.push(x);
    dis[x] = 1;
    int ans = 0;
    while(!q.empty())
    {
        int x = q.front();
        q.pop();
        ans ++;
        v.push_back(x);
        for(auto i : ve[x])
        {
            if(c[i] == 'W' && dis[i] == 0)
            {
                dis[i] = 1;
                q.push(i);
            }
        }
    }
    return ans;
}
 
int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> c[i];
    for(int i = 1; i < n; i ++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        ve[x].push_back(y);
        ve[y].push_back(x);
    }
    for(int i = 1; i <= n; i ++)
    {
        if(c[i] == 'W' && !dis[i])
        {
            int y = bfs(i);
            for(auto j : v)
            {
                dis[j] = y;
            }
        }
    }
    for(int i = 1; i <= n; i ++)
    {
        long long ans = 0;
        if(c[i] == 'B')
        {
            for(auto j : ve[i])
            {
                if(c[j] == 'W')
                {
                    sum += ans * dis[j] + dis[j];
                    ans += dis[j];
                }
            }
        }
    }
    cout << sum << "\n";
    return 0;
}

dfs:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int> G[100005];
ll sum,ans,t1,t2;
char a[100005];
/* 其实对于每个黑色节点而言,我们可以直接dfs搜索其周围有多少个白色节点连续的路径 */
void dfs(int p,int fa)
{
    if(a[p]=='B') return;
    sum++;
    for(int i=0;i<G[p].size();i++) if(G[p][i]!=fa) dfs(G[p][i],p);
}
int main()
{
    int n; 
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf(" \n%c",&a[i]);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    for(int i=1;i<=n;i++)
    {
        t1=t2=0;
        if(a[i]=='B')
        for(int j=0;j<G[i].size();j++)
        {
            sum=0;
            dfs(G[i][j],G[i][j]);
            ans+=sum;
            t1+=sum;
            t2+=sum*sum;
        }
        ans+=(t1*t1-t2)/2;
    }
    printf("%lld",ans);
}

Problem G: eli和字符串

题目大意: 现在有一串长度为 n n 的弹幕字符串,里面有3种字符, n i c o nico 记为 a a 分, n i c o n i niconi 记为 b b 分, n i c o n i c o n i niconiconi 记为 c c 分。已经计数过的字符不能重复计数( n i c o n i c o niconico 可以看做是两个 n i c o nico 记为 2 a 2a 分,或者一个 n i c o n i niconi 记为 b b 分,请你求出最大计分分数。

解题思路: 动态规划,令 d p [ i ] dp[i] 表示前 i i 个字符计数的最大值,那么可以得到以下转移方程:

d p [ i ] = m a x ( d p [ i ] , d p [ i 4 ] + a ) ,      i f ( s u b s t r i n g ( i 3 , i ) = = n i c o ) dp[i] = max(dp[i], dp[i-4]+a), \ \ \ \ if(substring(i - 3, i) == nico)

d p [ i ] = m a x ( d p [ i ] , d p [ i 6 ] + a ) ,      i f ( s u b s t r i n g ( i 5 , i ) = = n i c o n i ) dp[i] = max(dp[i], dp[i-6]+a), \ \ \ \ if(substring(i - 5, i) == niconi)

d p [ i ] = m a x ( d p [ i ] , d p [ i 10 ] + a ) ,     i f ( s u b s t r i n g ( i 9 , i ) = = n i c o ) dp[i] = max(dp[i], dp[i-10]+a), \ \ \ if(substring(i - 9, i) == nico)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 +10;
ll dp[N];

int main()
{
    string s;
    int n, a, b, c;
    cin >> n >> a >> b >> c;
    cin >> s;
    for(int i = 0; i < n; ++i) {
        if(i) dp[i] = max(dp[i], dp[i-1]); // 当第i个字符为n的时候,显然dp[i] = dp[i-1],因为此时这个n无法和前面的字符串进行组合。
        if(i >= 3 && s.substr(i - 3, 4) == "nico") 
            dp[i] = max(dp[i], dp[i-3] + a);
        if(i >= 5 && s.substr(i - 5, 6) == "niconi") 
            dp[i] = max(dp[i], dp[i - 5] + b);
        if(i >= 9 && s.substr(i - 9, 10) == "niconiconi") 
            dp[i] = max(dp[i], dp[i - 9] + c);
    }
    cout << dp[n - 1] << endl;
    return 0;
}

Problem H: nozomi和字符串

题目大意: 给你一个字符串,请你找出最短的一个至少包含 k k 个相同的某个字母的子串。

解题思路: 使用尺取法,双指针扫一遍即可。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;

int dict[256];
int get_max()
{
    int maxv = -1;
    for(int i = 'a'; i <= 'z'; ++i) {
        maxv = max(maxv, dict[i]); 
    }
    return maxv;
}
int main()
{
    int n, k;
    string s;
    cin >> n >> k;
    cin >> s;
    int l = 0, r = 0;
    int ans = INF, sum = 0;
    while(true) {
        while(r < n && sum < k) {
            ++dict[s[r++]];
            sum = get_max();
        }
        if(sum < k) break;
        // cout << l << " " << r << endl;
        ans = min(ans , r - l);
        --dict[s[l++]];
        sum = get_max();
    }
    if(ans == INF) ans = -1;
    cout << ans << endl;
    return 0;
}

Problem I: nico和niconiconi

题目大意: 给你一个01串,至多可以操作 k k 次,每次可以把 1 1 变成 0 0 或者把 0 0 变成 1 1 ,在操作后请你找出一个尽可能长的连续子串,子串上的所有字符都相同。

解题思路: 很显然,操作要么全部是把 1 1 变成 0 0 ,要么是全部把 0 0 变成 1 1 。我们分两种情况处理,对这两种情况取最大即可,计算的时候用尺取法,维持一个中间字符均为a的子序列,当右端点遇到b时,如果此时还有更改次数,就把b改成a,右端点++;如果此时已经没有了更改次数,就移动左端点,直到左端点越过第一个非a字母(相当于还原更改),然后右端点++。

代码:

#include <bits/stdc++.h>
using namespace std;

int n, k;
string s;

int deal(int a, int b)
{
    int l = 0, r = 0;
    int ans = 0, cnt = 0;
    while(l < n && r < n) {
        while(r < n && (s[r] == a || cnt < k)) {
            if(s[r] != a) ++cnt;
            r++;
        }
        ans = max(ans, r - l);
        while(s[l] == a && l <= r) ++l;
        ++l;
        --cnt;
    }
    return ans;
}

int main()
{

    cin >> n >> k >> s;
    cout << max(deal('0', '1'), deal('1', '0')) << endl; //把0改成1和把1改成0两种情况
    return 0;
}

Problem J: u’s的影响力

题目大意: 给你一个递推式 f ( x ) = f ( x 1 ) f ( x 2 ) a b f(x) = f(x-1)*f(x-2)*a^b ,让你求 f ( x ) % 1 e 9 + 7 f(x) \% 1e9+7

解题思路:

显然, f ( x ) f(x) 可以用 x x y y a a 这三个因子来表示,我们列出其中的一些项,发现规律:

f ( 1 ) = x f(1)=x f ( 2 ) = y f(2)=y f ( 3 ) = x y a b f(3)=xya^b f ( 4 ) = x y 2 a 2 b f(4)=xy^2a^{2b}

f ( 5 ) = x 2 y 3 a 4 b f(5)=x^2y^3a^{4b} f ( 6 ) = x 3 y 5 a 7 b f(6)=x^3y^5a^{7b}

我们观察到 x , y , a x,y,a 的幂是满足斐波那契数列的变形。

其中:

x x y y 的幂满足 f ( i ) = f ( i 1 ) + f ( i 2 ) f(i) = f(i - 1) + f(i - 2)

a a 的幂满足 f ( i ) = f ( i 1 ) + f ( i 2 ) + b f(i) = f(i - 1) + f(i - 2) + b

这样,我们可以将每个幂用矩阵快速幂求出来就行了,但是我们发现,这个幂根本无法存下来,那么,我们想到先对这个幂进行取模,根据费马小定理 a p 1 1 m o d ( p ) a^{p-1}≡1mod(p) ,其中 g c d ( a , p ) = 1 gcd(a, p)=1 ,因为 1 e 9 + 7 1e9+7 是一个质数,那么我们得到 a 1 e 9 + 6 1 m o d ( 1 e 9 + 7 ) a^{1e9+6}≡1mod(1e9+7) ,所以我们可以对幂模 1 e 9 + 7 1e9+7

ps:

比如 3 7 % 5 = 2 3^7 \% 5 = 2 ,由费马小定理 3 4 1 m o d ( 5 ) 3^4≡1mod(5) ,那么我们可以先对指数模4,得到 3 3 % 5 = 2 3^3 \% 5 = 2 ,这里相当于把 3 7 3^7 拆成了 3 3 3 4 3^3 * 3^4 ,因为 3 4 3^4 取模后为1,所以 1 3 3 ) % 5 = 2 (1 *3^3) \% 5 = 2

这样,我们将三个幂的矩阵形式求出来:

x x 的幂:

( 1 1 1 0 ) ( f ( i ) f ( i 1 ) ) = ( f ( i ) f ( i 1 ) ) = > ( 1 1 1 0 ) n 2 ( 0 0 1 0 ) = ( f ( n ) f ( n 1 ) ) \begin{pmatrix} 1 & 1\\ 1& 0 \end{pmatrix} * \begin{pmatrix} f(i)\\ f(i-1) \end{pmatrix} = \begin{pmatrix} f(i)\\ f(i-1) \end{pmatrix}=> \begin{pmatrix} 1 & 1\\ 1 & 0 \end{pmatrix}^{n-2}*\begin{pmatrix} 0 & 0\\ 1 & 0 \end{pmatrix} = \begin{pmatrix} f(n)\\ f(n-1) \end{pmatrix}

y y 的幂:

( 1 1 1 0 ) ( f ( i 1 ) f ( i 2 ) ) = ( f ( i ) f ( i 1 ) ) = > ( 1 1 1 0 ) n 2 ( 1 0 0 0 ) = ( f ( n ) f ( n 1 ) ) \begin{pmatrix} 1 & 1\\ 1& 0 \end{pmatrix} * \begin{pmatrix} f(i-1)\\ f(i-2) \end{pmatrix} = \begin{pmatrix} f(i)\\ f(i-1) \end{pmatrix}=> \begin{pmatrix} 1 & 1\\ 1 & 0 \end{pmatrix}^{n-2}*\begin{pmatrix} 1 & 0\\ 0 & 0 \end{pmatrix} = \begin{pmatrix} f(n)\\ f(n-1) \end{pmatrix}

a a 的幂:

( 1 1 1 1 0 0 0 0 1 ) ( f ( i 1 ) f ( i 2 ) b ) = ( f ( i ) f ( i 1 ) b ) = > ( 1 1 1 1 0 0 0 0 1 ) n 3 ( b 0 0 0 0 0 b 0 0 ) = ( f ( n ) f ( n 1 ) n ( n 2 ) ) \begin{pmatrix} 1 & 1 & 1\\ 1 & 0 & 0\\ 0 & 0 & 1 \end{pmatrix} *\begin{pmatrix} f(i-1)\\ f(i-2)\\ b \end{pmatrix} = \begin{pmatrix} f(i)\\ f(i-1)\\ b \end{pmatrix} => \begin{pmatrix} 1 & 1 & 1\\ 1& 0& 0\\ 0& 0& 1 \end{pmatrix}^{n-3} * \begin{pmatrix} b & 0 & 0\\ 0& 0& 0\\ b& 0& 0 \end{pmatrix} = \begin{pmatrix} f(n)\\ f(n-1)\\ n(n-2) \end{pmatrix}

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int p = 1e9 + 7;
 
struct M{
    LL m[2][2];
};
 
M mul(M a, M b){
    M c;
    c.m[0][0] = c.m[0][1] = c.m[1][0] = c.m[1][1] = 0;
    for(int i = 0; i < 2; ++ i){
        for(int j = 0; j < 2; ++ j){
            if(a.m[i][j] == 0) continue;
            for(int k = 0; k < 2; ++ k){
                c.m[i][k] = (c.m[i][k] + a.m[i][j] * b.m[j][k] % (p - 1)) % (p - 1);
            }
        }
    }
    return c;
}
 
LL Mpow(LL n){
    M a, k;
    a.m[0][0] = a.m[0][1] = a.m[1][0] = 1; a.m[1][1] = 0;
    k.m[0][0] = k.m[1][1] = 1; k.m[0][1] = k.m[1][0] = 0;
    while(n){
        if(n & 1) k = mul(a, k); a= mul(a, a); n >>= 1;
    }
    return k.m[0][1] % (p - 1);
}
 
LL fpow(LL a, LL b){
    a %= p;
    LL ans = 1;
    while(b){
        if(b & 1) ans = ans * a % p; a = a * a % p; b >>= 1;
    }
    return ans;
}
 
int main(){
    LL n, x, y, a, b, k;
    cin >> n >> x >> y >> a >> b;
    k = fpow(a, b);
    if(n == 1) cout << x % p;
    else if(n == 2) cout << y % p;
    else if(n == 3) cout << ((x % p) * (y % p) % p) * k % p;
    else{
        x = fpow(x, Mpow(n - 2) + p - 1);
        y = fpow(y, Mpow(n - 1) + p - 1);
        k = fpow(k ,Mpow(n) - 1 + p - 1);
        cout << (x * y % p) * k % p;
    }
    return 0;
}
发布了128 篇原创文章 · 获赞 20 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/u011544909/article/details/104203013