【题解】牛客练习赛113题解

写在前面的话

这是第二次出练习赛,上一场出的练习赛是练习赛100,这场相比上场更加平衡了一些难度,看上去榜还是挺美的。

A 小红的基环树

知识点:图论,构造,贪心

难度:800

显然,最小直径是不会超过2的。我们可以先构造一棵菊花树(所有节点连在同一个节点上),然后连接两个叶子,这样就构造出了一个直径为2的基环树。

特殊的,当时,基环树的直径只有1。

参考代码(python):


if input() == "3":
    print(1)
else:
    print(2)

B 小红的回文串

知识点:字符串,乘法原理

难度:800

回文串的性质即:对于每一个。因此我们可以对每个位置进行讨论:

如果两个位置都不是'?',且两个字符不相等,则直接无解。

如果两个位置都不是'?',且两个字符相等,则答案不变动。

如果两个位置有一个'?',另一个为字母,则答案不变动。

如果两个位置都是'?'(或者恰好是字符串中间的那个'?'),则答案乘以26。

参考代码:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 7 + 1e9;
string s;
int main() {
    cin >> s;
    int n = s.size();
    LL ans = 1;
    for (int i = 0, j = n - 1; i <= j; ++i, --j) {
        if (s[i] == '?' || s[j] == '?') {
            if (s[i] == '?' && s[j] == '?') {
                ans = ans * 26 % mod;
            }
        } else {
            if (s[i] != s[j]) {
                ans = 0;
                break;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

C 小红的数组操作(easy)

知识点:枚举

难度:1300

题意:我们注意到,在本难度中,每次元素只会加1。因此我们可以枚举“减”的次数,然后直接计算出还需要几次“加1”操作。

参考代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[202020];
signed main(){
    int n,p,x,q,y,i;
    cin>>n>>p>>x>>q>>y;
    int s=0;
    for(i=0;i<n;i++)cin>>a[i],s+=a[i];
    s%=n;
    int mi=1e18;
    for(i=0;i<n;i++){
        mi=min(mi,((n-(s-i*y)%n+n)%n)%n*p+q*i);
    }
    cout<<mi;
}

D 小红的数组操作(hard)

知识点:枚举/最短路

难度:1700

方法1:枚举

我们仍然用easy难度中的枚举方法。不同的是,我们需要知道每次加,得到目标的最小次数。有两种方案可以求:

① 解同余方程,可以直接使用exgcd来解决。

② 我们直接预处理出在模意义下的值,这样即可O(1)调用了。

方法2:最短路

我们把模等于0到的每个值作为一个节点,那么可以建一个带权图:的权值为的权值为。然后跑最短路即可。

参考代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
vector<pair<int,int>> edge[200005];
int dist[200005];
int vis[200005];
int n,p,x,q,y;
int a[200005];

void dijk(int x)
{
	for(int i = 1;i<=n;++i)
		dist[i] = 1e17;
	dist[x] = 0;
	priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;
	q.push({0,x});
	while(!q.empty())
	{
		int now = q.top().second;
		int w = q.top().first;
		
		q.pop();
		if(vis[now])
			continue;
		vis[now] = 1;
		for(auto v:edge[now])
		{
			if(vis[v.first])
				continue;
			if(w+v.second<dist[v.first])
			{
				dist[v.first] = w+v.second;
				q.push({dist[v.first],v.first});
			}
		}		
	}
}
signed main()
{
	cin>>n>>p>>x>>q>>y;
	int sum = 0;
	for(int i = 1;i<=n;++i)
	{
		cin>>a[i],sum+=a[i],sum%=n; 
	}
	for(int i = 0;i<n;++i)
	{
		int xx = (i+x)%n;
		edge[i+1].push_back({xx+1,p});
		int yy = (((i-y)%n)+n)%n;
		edge[i+1].push_back({yy+1,q});
	 } 
	dijk(sum+1);
	if(dist[1]>=1e15)
		cout<<-1<<'\n';
	else
		cout<<dist[1]<<'\n';
	return 0;
}

E 小红走排列

知识点:构造

难度:1800

我们先找到最小步数的排列:,总步数为

然后怎么使得步数加1呢?直接将最后两位翻转即可。数组变成

同理,我们希望使得步数加2的话,需要构造的排列为

……

当我们翻转完最后个数时,得到的排列为。此排列的总步数为

同理,我们在上面这个排列的基础上再翻转后个数的前缀,可以得到最多的步数为。对应的排列是

后面我们按这种方式,即可构造出任意想要的步数的排列。

参考代码:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 3 + 1e5;
int n;
LL k;
int ans[N];
int main() {
    cin >> n >> k;
    k -= n - 1;
    vector<int> seq;
    seq.push_back(1);
    seq.push_back(n);
    for (int l = 2, r = n - 1, flag = 1; l <= r && k;) {
        if (flag) {
            LL t = min<LL>(k, seq.back() - l);
            l = seq.back() - t;
            k -= t;
            seq.push_back(l);
            ++l;
        } else {
            LL t = min<LL>(k, r - seq.back());
            r = seq.back() + t;
            k -= t;
            seq.push_back(r);
            --r;
        }
        flag = !flag;
    }
    seq.erase(seq.begin());
    int t = n;
    for (auto it = seq.rbegin(); it != seq.rend(); ++it) {
        ans[*it] = t--;
    }
    t = 1;
    for (int i = 1; i <= n; ++i) {
        if (!ans[i]) {
            ans[i] = t++;
        }
    }
    for (int i = 1; i <= n; ++i) {
        cout << ans[i] << ' ';
    }
    return 0;
}

F&&G 小红的好子序列(easy&&hard)

知识点:dp,组合数学,容斥

难度:easy 1800/ hard 2100

我们先考虑的做法。首先枚举“哪个数作为次数不小于一半的元素”,然后枚举“该元素的数量”和“剩余元素的数量”,即可计算出贡献。这样的综合复杂度为

然后我们考虑如何优化该代码。当我们需要枚举“剩余元素的数量”的时候,可以批量进行统计。具体的,若当前元素取了个,剩余的元素取个时,带来的贡献为,最终的总贡献为,我们可以批量统计的系数之和(类似前缀和的思路,计算的前项和)。

值得注意的是,类似{1,1,2,2}这样的数组我们在统计时计算了两次,因此需要利用容斥的思想减掉该部分的贡献。

参考代码:

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 7 + 1e9;
const int N = 3 + 1e5;
int n, a[N];
map<int, int> mp;
LL inv[N], fac[N], invFac[N];
LL c(int n, int m) {
    if (m > n) {
        return 0;
    }
    return fac[n] * invFac[m] % mod * invFac[n - m] % mod;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        ++mp[a[i]];
    }
    vector<int> b;
    for (auto &i : mp) {
        b.push_back(i.second);
    }
    sort(b.rbegin(), b.rend());
    fac[0] = 1;
    invFac[0] = 1;
    inv[1] = 1;
    fac[1] = 1;
    invFac[1] = 1;
    for (int i = 2; i <= n; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        invFac[i] = invFac[i - 1] * inv[i] % mod;
    }
    LL ans = 0;
    for (int i = 1; i <= b[0]; ++i) {
        LL cnt = 0;
        for (auto &j : b) {
            if (j < i) {
                break;
            }
            ans -= c(j, i) * cnt % mod;
            cnt = (cnt + c(j, i)) % mod;
        }
    }
    for (int i : b) {
        LL cnt = 1;
        for (int j = 1; j <= i; ++j) {
            cnt = (cnt + c(n - i, j)) % mod;
            ans += c(i, j) * cnt % mod;
        }
    }
    ans = (ans % mod + mod) % mod;
    cout << ans << endl;
    return 0;
}

H 小红的括号串权值

知识点:树,字符串

难度:2400

我们讨论一个子串的权值,即求它内部所有括号对的“深度”之和。所谓括号对的深度,即其被嵌套的层数。例如,(()())最外层的那对括号深度为0,里面的两对括号深度为1。

那么怎么求所有子串的权值呢?我们先观察括号串的两种生成方式:

  1. 在一个合法括号串两侧加一对括号,例如,(()())。这种生成括号的方式,会使得内部每个括号对的权值加1,外部那个括号的权值为0。

  2. 将两个合法的括号串拼接,例如,(())()。这种生成括号串的方式,即将两个括号串的权值求和。

我们在计算所有子串权值时,将若干合法串并列,如果要取这一层的子串,一定是取若干个这一层连续的部分(每部分必须取完),这样才能保证合法。

因此我们可以建一棵树,对于每一对括号,它内部的括号即为它的儿子。这样意味着我们必须取“同一层若干个连续的并列子树”,这样才是一个合法解。每个节点存该对括号组成的那个子串的贡献。

那么我们统计权值可以这样统计:父亲的权值为所有儿子的权值之和加上子节点的数量(因为外面每加一层括号,会使得内部所有括号的权值加1)。那么最终每个节点贡献的权值为,其中代表该节点同层左边的节点数,代表该节点同层右边的节点数。

#include<bits/stdc++.h>
using namespace std;
#define int long long
vector<int>g[302020];
int vis[303030];
int mod=1e9+7;
void add(int x,int y){
    g[x].push_back(y);
    g[y].push_back(x);
}
int sz[303030];
int dp[303030],res;
void dfs(int x,int pr){
    vis[x]=1;
    sz[x]=1;
    for(auto i:g[x]){
        if(i==pr)continue;
        dfs(i,x);
        sz[x]+=sz[i];
    }
    int cc=0;
    for(auto i:g[x]){
        if(i==pr)continue;
        cc++;
        dp[x]+=sz[i]+dp[i];
        dp[x]%=mod;
    }
    int j=0;
    for(auto i:g[x]){
        if(i==pr)continue;
        res+=1ll*dp[i]*(j+1)%mod*(cc-j)%mod;
        res%=mod;
        j++;
    }
}
signed main(){
    string s;
    cin>>s;
    int i;
    s=')'+s;
    stack<int>st;
    st.push(0);
    int cnt=0;
    for(i=1;i<s.length();i++){
        if(s[i]=='(')cnt++,st.push(i);
        else {
            cnt--;
            if(cnt<0){
                cnt=0;
                st.pop();
                st.push(i);
                continue;
            }
            int temp=st.top();
            
            st.pop();
            add(temp,st.top());
        }
        
    }
    res=0;
    for(i=0;i<=s.length();i++){
        if(!vis[i])dfs(i,-1);
    }
    cout<<res;
}
全部评论
F&&G题题解的公式格式好像挂了
8 回复 分享
发布于 2023-07-08 12:58 上海
D的exgcd挂了。。
1 回复 分享
发布于 2023-07-08 08:01 山东
点赞 回复 分享
发布于 2023-07-08 03:23 四川
E题我打表发现好像k能做到比原来的数据范围更大,但是不懂怎么构造,有没有佬教教
点赞 回复 分享
发布于 2023-07-08 11:04 陕西

相关推荐

最近又搬回宿舍了,在工位坐不住,写一写秋招起伏不断的心态变化,也算对自己心态的一些思考表演式学习从开始为实习准备的时候就特别焦虑,楼主一开始选择的是cpp后端,但是24届这个方向已经炸了,同时自己又因为本科非92且非科班,所以感到机会更加迷茫。在某天晚上用java写出hello&nbsp;world并失眠一整晚后选择老本行干嵌入式。理想是美好的,现实情况是每天忙但又没有实质性进展,总是在配环境,调工具,顺带还要推科研。而这时候才发现自己一直在表演式学习,徘徊在设想如何展开工作的循环里,导致没有实质性进展。现在看来当时如果把精力专注在动手写而不是两只手端着看教程,基本功或许不会那么差。实习的焦虑5月,楼主...
耶比:哲学上有一个问题,玛丽的房间:玛丽知道眼睛识别色彩的原理知道各种颜色,但是她生活在黑白的房间里,直到有一天玛丽的房门打开了她亲眼看到了颜色,才知道什么是色彩。我现在最大可能的减少对非工作事情的思考,如果有一件事困扰了我, 能解决的我就直接做(去哪里或者和谁吵架等等……),解决不了的我就不想了,每一天都是最年轻的一天,珍惜今天吧
投递比亚迪等公司10个岗位 > 秋招被确诊为…… 牛客创作赏金赛
点赞 评论 收藏
分享
无敌虾孝子:喜欢爸爸还是喜欢妈妈
点赞 评论 收藏
分享
评论
11
3
分享
牛客网
牛客企业服务