AcWing第78场周赛

时间:2022-11-22 22:09:54

今天想起来了,就补一下吧~

商品分类

货架中摆放着 n 件商品,每件商品都有两个属性:名称和产地。

当且仅当两件商品的名称和产地都相同时,两件商品才视为同一种商品。

请你统计,货架中一共有多少种不同的商品。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含两个字符串,分别表示一件商品的名称和产地。

输入字符串的长度范围为 [1,10],且仅包含小写字母。

输出格式
一个整数,表示商品种类数量。

数据范围
前 4 个测试点满足 1≤n≤5。
所有测试点满足 1≤n≤100。

输入样例1:
5
b y
m r
b y
m y
m g
输出样例1:
4
输入样例2:
3
abc def
abc def
abc def
输出样例2:
1


#include <bits/stdc++.h>
using namespace std;
const int N = 110;
struct C
{
    string a,b;
}C[N];
int ans,n;
int main()
{
    cin>>n;
    ans=n;
    for (int i=0;i<n;i++)
    {
        string a,b;
        cin>>a>>b;
        C[i].a=a,C[i].b=b;
        for (int j=0;j<i;j++)
        {
            if (C[j].a==C[i].a&&C[j].b==C[i].b)
            {
                ans--;
                break;
            }
        }
    }
    ans=max(1,ans);
    cout<<ans<<endl;
    return 0;
}

字符串

给定一个由小写字母构成的字符串 s。

如果字符串中存在两个字母相同且相邻,则称它们为相同连续字母对。

我们不希望 s 中存在相同连续字母对。

所以,每当在 s 中发现一个相同连续字母对时,就应当将这对字母从 s 中删除,如果删除某一对后,出现了新的相同连续字母对,则新的对也应当被删除。

总之,最终得到的字符串中不能存在相同连续字母对。

输出最终得到的字符串。

可以证明,不论按何种顺序删除相同连续字母对,最终得到的字符串都是一样的。

输入格式
共一行,一个由小写字母构成的字符串 s。

输出格式
输出最终得到的字符串。

保证结果不为空。

数据范围
前 5 个测试点满足 1≤|s|≤20。
所有测试点满足 1≤|s|≤2×105。

输入样例1:
aabbcddddefggbbaa
输出样例1:
cef
输入样例2:
abcddcef
输出样例2:
abef
输入样例3:
abacabaabacabaa
输出样例3:
a


方法一:栈的思想,代码简洁

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int main()
{
    string s,ans;
    cin>>s;
    for (auto c:s)
        if (ans.size()&&ans.back()==c) ans.pop_back();
        else ans+=c;
    
    cout<<ans<<endl;
    return 0;
}

双指针算法(比赛中只想到这么写了,简单但代码长些)

#include <bits/stdc++.h>
using namespace std;
const int N = 2*100010;
string ans;
int main()
{
    string s;
    cin>>s;
    for (int i=0,j=1;i<s.size();)
    {
        if (s[i]==s[j])
        {
             i+=2,j+=2;
             while  (s[i]==ans.back())
             {
                 ans.pop_back();
                 i++,j++;
             }
        }
        else 
        {
            ans.push_back(s[i]);
            i++,j++;
        }
    }
    cout<<ans<<endl;
    return 0;
}

排队

n 个小朋友排成一排,从左到右依次编号为 1∼n。

第 i 个小朋友的身高为 hi。

虽然队伍已经排好,但是小朋友们对此并不完全满意。

对于一个小朋友来说,如果存在其他小朋友身高比他更矮,却站在他右侧的情况,该小朋友就会感到不满。

每个小朋友的不满程度都可以量化计算,具体来说,对于第 i 个小朋友:

如果存在比他更矮且在他右侧的小朋友,那么他的不满值等于其中最靠右的那个小朋友与他之间的小朋友数量。
如果不存在比他更矮且在他右侧的小朋友,那么他的不满值为 −1。
请你计算并输出每个小朋友的不满值。

注意,第 1 个小朋友和第 2 个小朋友之间的小朋友数量为 0,第 1 个小朋友和第 4 个小朋友之间的小朋友数量为 2。

输入格式
第一行包含整数 n。

第二行包含 n 个整数 h1,h2,…,hn。

输出格式
共一行,输出 n 个整数,第 i 个整数为第 i 个小朋友的不满值。

数据范围
前 5 个测试点满足 2≤n≤5。
所有测试点满足 2≤n≤105,1≤hi≤109。

输入样例1:
6
10 8 5 3 50 45
输出样例1:
2 1 0 -1 0 -1
输入样例2:
7
10 4 6 3 2 8 15
输出样例2:
4 2 1 0 -1 -1 -1
输入样例3:
5
10 3 1 10 11
输出样例3:
1 0 -1 -1 -1


方法一:构建后缀数组,再用二分找到数组中小于当前数的最右边的位置

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int h[N],f[N];
int n;
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&h[i]); 
    
    f[n]=h[n]; // 最后一个位置初始化
    // 从后往前更新第i个位置右侧中比h[i]最小的数,此时的f是非严格单调递增的
    for (int i=n-1;i;i--) f[i]=min(h[i],f[i+1]);
    // 对每个小孩的位置进行求解
    for (int i=1;i<=n;i++)
    {
        int l=i,r=n;
        while (l<r)
        {
            int mid = l+r+1>>1;
            if (f[mid]<h[i]) l=mid;
            else r=mid-1;
        }
        printf("%d ",r-i-1);
    }
    return 0;
}

方法二:单调栈的思想,再加二分。感觉更简洁些

#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int a[N],s[N];
int n;
int top=-1;
int main()
{
    scanf("%d",&n);
    for (int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        while (top>=0&&a[s[top]]>=a[i]) top--; // 只要栈不为空且栈顶元素大于等于新的元素,就出栈
        
        s[++top]=i; // 注意:栈中存储的是元素下标,这是便于后面通过下标对应原数组的元素
    }
// 至此,栈中的元素是严格单调递增的,可以二分了。
    for (int i=0;i<n;i++)
    {
        int l=0,r=top; // 左边界为栈底,右边界为栈顶。目的是寻找小于当前元素的最右侧的元素的位置
        while (l<r)
        {
            int mid =l+r+1>>1;
            if (a[s[mid]]>=a[i]) r=mid-1; 
            else l=mid;
        }
// 这里需要特判一下,也就是这个位置是在当前元素右侧的,且这个元素是一定小于当前元素的,才会输出两个元素位置之间的元素个数
        if (s[r]>i&&a[s[r]]<a[i]) printf("%d ",s[r]-i-1);
        else printf("-1 ");
    }
    return 0;
}

总结

单调栈和单调队列还是用的不少的,得多加练习,孰能生巧。另外,这几次的比赛感觉双指针真香hhh