无聊听了蓝桥学苑的系列网课,有些东西还是值得学习的。
-
哈希表的运用(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;
}