枚举专题

无聊听了蓝桥学苑的系列网课,有些东西还是值得学习的。

 

  • 哈希表的运用(unordered_map)

c++ 11的新东西,map是基于红黑树的,这个是基于哈希表的,所以单次复杂度是O (1),但是建表的时候时间复杂度可能会退化。

题目1链接:http://hihocoder.com/problemset/problem/1494?sid=1342022

题目大意:略

题解:求最少穿过的墙砖数,就是尽量从两块砖之间的缝走,也就是求对于哪个x坐标,使得这条线上缝最多,可以用哈希表来统计。

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

int n,c;
unordered_map<int,int> mp;

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>c;
        int temp = 0;
        for(int j=0;j<c;j++)
        {
            int x;
            cin>>x;
            temp+=x;
            if(j!=c-1)
                mp[temp]++;
        }
    }
    int maxn = 0;
    for(auto i:mp)
        maxn = max(i.second,maxn);
    cout<<n-maxn<<endl;
    return 0;
}

题目2链接:http://hihocoder.com/problemset/problem/1505?sid=1342046

题目大意:某人有N袋金币,其中第i袋内金币的数量是Ai。现在他决定选出2袋金币送给小Hi,再选2袋金币送给小Ho,同时使得小Hi和小Ho得到的金币总数相等。他想知道一共有多少种不同的选择方法

题解:

       因为每个人是选两袋金币,所以我们可以将任意两袋金币的和枚举出来用哈希表存下来,然后看相同的结果有多少个,再利用容斥原理算。

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

unordered_map<int,int> cnt1,cnt2;

long long n,ans;
int a[1005];

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
        cnt1[a[i]]++; //cnt1记录金币为a[i]的袋数有多少袋
    }
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
            cnt2[a[i]+a[j]]++; //cnt2统计任意两袋加起来的金币数有多少袋
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
        {
            if(a[i]!=a[j])
                ans += cnt2[a[i]+a[j]]-cnt1[a[i]]-cnt1[a[j]]+1; //如果两袋金币数不相等,减去cnt1的两种表示如果已经用了第i袋,那么所有金币数量和第j戴相等的都要去掉,因为i不能重复用。。最后+1是因为去掉两次,还要再加上一次
            else
                ans += cnt2[a[i]+a[j]]-cnt1[a[i]]-cnt1[a[j]]+3;
        }
    cout<<ans<<endl;
    return 0;
}

  • 双指针

题目1链接:http://hihocoder.com/problemset/problem/1745?sid=1342311

题目大意:n张卡片,每个卡片上面有个数字;m个百搭卡;要求顺子长度为k。要求用以上条件组成最大顺子,并求出该顺子中最大的那个数。

题解:

        从大往小枚举,注意其中的数学关系------ 需要的百搭卡为a[j]-a[i]-(j-i)。主要还是双指针的运用,用i和j分别指向顺子的两端。

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

const int maxn = 100000+5;

int a[maxn];
int main()
{
    long long n,m,k;
    cin>>n>>m>>k;
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    sort(a,a+n);
    for(int i=n-1,j=n-1;i>=0;i--)
    {
        long long temp = a[j]-a[i]-(j-i);  //需要的百搭卡
        while(temp>m)
        {
            j--;
            temp = a[j]-a[i]-(j-i);
        }
        if(a[j]-a[i]+1+m-temp>=k)
        {
            cout<<a[j]+(m-temp)<<endl; //需要这么改是因为可以把多的百搭卡放到a[j]后面
            break;
        }
    }
    return 0;
}

题目链接:http://hihocoder.com/problemset/problem/1514?sid=1342332

题目大意:有三个序列,要求从三个序列中各挑一个数,使得D=|Ai-Bj|+|Bj-Ck|+|Ck-Ai|最小。

题目思路:先排序,然后二分查找。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;

const int inf = 0x3f3f3f3f;
const int INF = 0x7fffffff;
const int maxn = 200005;

inline void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

inline void print(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

int n,m,l,a[maxn],b[maxn],c[maxn];
ll ans = INF;

void fun(ll x,ll y,ll z)
{
    ll d = abs(x-y)+abs(x-z)+abs(y-z); //参数设置为long long是因为如果用int的话会爆负数
    ans = min(d,ans);
}

int main()
{
    scanf("%d%d%d",&n,&m,&l);
    for(int i=1;i<=n;i++)
        read(a[i]);
    for(int i=1;i<=m;i++)
        read(b[i]);
    for(int i=1;i<=l;i++)
        read(c[i]);
    sort(a+1,a+n+1);
    sort(b+1,b+m+1);
    sort(c+1,c+l+1);
    a[0] = b[0] = c[0] = -INF;
    a[n+1] = b[m+1] = c[l+1] = INF;
    for(int i=1,j=0,k=0;i<=n;i++) //对任意一个a,找到b中第一个大于a[i]的数,然后比较b[j]和b[j+1]与a[i]哪个更近,c数组同理
    {
        while(b[j+1]<a[i]) j++;
        while(c[k+1]<a[i]) k++;
        fun(a[i],b[j],c[k]);
        fun(a[i],b[j+1],c[k]);
        fun(a[i],b[j],c[k+1]);
        fun(a[i],b[j+1],c[k+1]);
    }
    cout<<ans<<endl;
    return 0;
}

题目链接:http://hihocoder.com/problemset/problem/1607?sid=1347017

题目大意:

Handbook是H星人的一家社交网络。Handbook中共有N名用户,其中第i名用户的年龄是Ai。  

根据H星人的文化传统,用户i不会给用户j发送好友请求当且仅当:

1. Aj < 1/8 * Ai + 8 或者  

2. Aj > 8 * Ai + 8 或者  

3. Ai < 88888 且 Aj > 88888  

其他情况用户i都会给用户j发送好友请求。  

你能求出Handbook总计会有多少好友请求吗?

题解:

       双指针枚举大法好。先排个序然后枚举所有满足条件的l和r区间。

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

const int maxn = 100005;
typedef long long ll;

ll n,a[maxn];
ll ans=0;

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
    sort(a,a+n);
    int l = 0,r = 0;
    for(int i=0;i<n;i++)
    {
        while(l<n && a[l]*8<a[i]+64) l++; //左区间求第一个满足条件的l(尽可能小)
        while(r+1<n && a[r+1]<=a[i]*8+8 && (a[i]>=88888||a[r+1]<=88888)) r++; //右区间求第一个不满足条件的r ,注意,这里中间有的是&&,表示要同时满足这两个条件。如果用两个while的话可能只满足了其中的一个
        if(l<=r) {
            ans += r-l+1;
            if(i>=l&&i<=r) ans--; //如果i在区间内,它不可能给自己发消息,所以减去1
        }
    }
    cout<<ans<<endl;
    return 0;
}

  • 前缀和优化

题目链接:http://hihocoder.com/problemset/problem/1534?sid=1347176

题目大意:给定一个序列,要求分成三段,每一段求和,并且任意两段和的绝对值差<=1。

题解:

        可以用双重循环,这里复杂度会超时,就不说了。

         也可以用单层循环,枚举s3的值,由于s1只能在[s3-1,s3+1]之间,所以再枚举s1的值,剩余s2的值是可以之间算出来的,然后判断是否符合条件。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <unordered_map>>
#include <algorithm>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;

const int inf = 0x3f3f3f3f;
const int maxn = 100005;
#define eps 1e-8

inline void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

inline void print(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

ll n,a[maxn],pre[maxn],ans;
unordered_map<ll,ll> cnt;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    pre[0] = 0;
    for(int i=1;i<=n;i++)
    {
        pre[i] = pre[i-1]+a[i];
        if(i<n) cnt[pre[i]]++; //不把最后一个加入哈希表,因为它只能属于s3
    }
    ll s3 = 0;
    for(int q=n-1;q>=2;q--)
    {
        s3 += a[q+1];
        cnt[pre[q]]--; //因为是分成3段,所以要把第q个剔除出哈希表
        for(ll s1=s3-1;s1<=s3+1;s1++)
        {
            ll s2 = pre[n]-s1-s3;
            if(abs(s2-s1)<=1&&abs(s2-s3)<=1)
                ans += cnt[s1];
        }
    }
    cout<<ans<<endl;
    return 0;
}


题目链接2:http://hihocoder.com/problemset/problem/1604?sid=1347205

题目大意:若干只股票,买买卖卖,求最大收益。刚开始金币无限,可以同时持有若干只股票。

题解:

        第一想法还是找上升段,只在上升段顶点卖,但这样是有问题的。比如1,2,1,3,,这就应该在3位置卖。

        所以用后缀最大值来求,用pre[i]记录从i到n中最大的a[i],只需要在那个点卖就行了。如果这个点就是最大的也没有关系,相当于在这里买了一次又卖了一次。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;

#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;

const int inf = 0x3f3f3f3f;
const int maxn = 1000005;
#define eps 1e-8

inline void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

inline void print(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

ll n,a[maxn],pre[maxn];

int main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
        cin>>a[i];
    pre[n] = a[n];
    for(int i=n-1;i>=1;i--)
        pre[i] = max(pre[i+1],a[i]);
    ll ans = 0;
    for(int i=1;i<=n;i++)
        ans += pre[i]-a[i];
    cout<<ans<<endl;
    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值