Point 1 双指针
Point 2 实数二分模版
Point 3 小数二分模版
Point 4 前缀与差分
Point 5 倍增与ST表
NO.1
题目描述:
题意分析:给一个序列,求
解题思路:
如果直接用二重循环进行暴力求解会超时,我们通过思考log2(sum)可能得取值,发现sum最大为1e10,所以log2(sum)最大为33,枚举这个值,检查哪些区间会取到这个值。枚举的log2值为res,可以先预处理出pow2数组,pow2[i]代表2^i,这样可以发现sum要大于等于pow2[res]且小于pow2[res + 1],才能使log2(sum)等于res,
注意点:题目认为log2(0) = 0
复杂度分析:O(n)
带注释的代码:
#include <iostream>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
ll pow2[40]; // 预处理出2^i
void solve(){
int n;
cin >> n;
ll presum[100005] = {0}; // 前缀和,sum(l,r) = presum[r] - presum[l - 1]
for (int i = 1; i <= n;i++){
ll x;
cin >> x;
presum[i] = presum[i - 1] + x;
}
ll ans = 0;
// 发现sum最大为1e10,所以log2(sum)最大值为33,枚举log2的值(保险起见枚举到35)
for (int res = 0; res <= 35;res++){
// 发现sum要大于等于pow2[res]且小于pow2[res + 1],才能使log2(sum)等于res
// 定义快慢指针i,j,j1往右移动到第一个presum[j1] - presum[i - 1]>=pow2[res]的位置,j2移动到第一个presum[j2] - presum[i - 1] >= pow2[res+1]的位置
// i所对应的数就在[j1,j2 - 1]中,在i位置更新j2 - j1
ll i = 1, j1 = 1,j2 = 1;
for (i = 1; i <= n;i++){
j1 = max(j1,i);j2 = max(j2,i); // j1,j2至少要从i开始
while (j1 <= n && presum[j1] - presum[i - 1] < pow2[res]){
j1++;
}
while (j2 <= n && presum[j2] - presum[i - 1] < pow2[res + 1]){
j2++;
}
ll sum1 = presum[j1] - presum[i - 1];
ll sum2 = presum[j2 - 1] - presum[i - 1];
if((sum1 >= pow2[res] && sum1 < pow2[res + 1]) && (sum2 >= pow2[res] && sum2 < pow2[res + 1])){
ll add = (res + 1) * (2 * i + j1 + j2 - 1) * (j2 - j1) / 2; // 等差数列求和
ans += add;
}
}
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
pow2[0] = 0; //题目认为log2(0) = 0
pow2[1] = 2;
for(int i = 2 ; i <= 36 ; i++){
pow2[i] = pow2[i - 1] * 2;
}
int t = 1;
cin >> t;
while(t--){
solve();
}
return 0;
}
NO.2
题目描述:
题意分析:按照订单的先后顺序依次为每份订单分配教室并判断是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
解题思路:利用差分数组存每天的教室使用情况,求前缀和,如果发现不符合要求,就从后往前撤回订单,直到每天都符合要求,那么撤回的最后一个(也就是最靠前的一个)即为ans
复杂度分析:O(n+m)
带注释的代码:
#include<iostream>
#include<stdio.h>
const int I=1000005;
int a[I],c[I],l[I],r[I],n,m,x=-1;
long long cf[I];
long long sum;
bool flag=1;
using namespace std;
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++){ //差分数组
cin>>c[i]>>l[i]>>r[i];
cf[l[i]]+=c[i];
cf[r[i]+1]-=c[i];
}
int j=m;//从后往前推,只要发现删除哪个后正好符合要求,则其为ans
for(int i=1;i<=n;i++){
sum+=cf[i];//计算每个教室的使用情况
if(sum>a[i]){
while(sum>a[i]){ //从后往前撤回消息
cf[l[j]]-=c[j];
cf[r[j]+1]+=c[j];
if(l[j]<=i&&i<=r[j])//如果一个请求包含了第i个教室,则删除它会影响sum的值
sum-=c[j];
j--;
}
if(flag)x=j,flag=0;//更新x
else
x=min(x,j);
}
}
if(x==-1)cout<<"0";
else cout<<"-1"<<endl<<x+1;
return 0;
}
NO.3
题目描述:
题意分析:求对每组数据进行操作后的最大公约数。
解题思路:要想求 n 个数的最大公约数,可以将这 n 个数进行素因子分解,然后找出这 n 个分解式子中 所有出现过的素因子 a 与 该素因子的最小次幂 p ,然后将所有的 a p 相乘即为最大公约数
由题意可知,每个数的初始值为1,素因子只可能是 2 或 3,所以就记录 a[i] 乘了多少次2 和多少次 3 就相当于把 a[i] 素因子分解了,最后去统计 2 的最小次幂和 3的最小次幂 然后相乘 (可用快速幂,记得取余)怎么统计 a[i] 乘了多少次 2 和 3 呢? ,用book数组统计book[L]++,book[R+1]–,最后计算前缀和
复杂度分析:O(n+m)
带注释的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
long long book[4][100010];
long long mod=998244353;
ll Quick_Power(ll a,ll b)//快速幂{
ll res = 1;
a %= mod;
while(b){
if(b&1){
res = (res * a) % mod;
}
a = (a * a) % mod;
b >>= 1;
}
return res%mod;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
cin>>n>>m;
for(int i=0;i<=n+1;i++){
book[2][i]=book[3][i]=0;
}
int r,l,tag;
for(int i=0;i<m;i++){
cin>>l>>r>>tag;
book[tag][l]++;
book[tag][r+1]--;
}
for(int i=1;i<=n;i++){
book[2][i]+=book[2][i-1];
book[3][i]+=book[3][i-1];
/*由于初始值为 1 ,所以每个数的素因子只有 2 和 3 ,
通过该步骤得出每个数的素因子的幂次,找到2的最小幂次 和 3 的最小幂次,
乘积就是最大公约数 */
}
long long mina=0x3f3f3f3f,minb=0x3f3f3f3f;
for(int i=1;i<=n;i++){
mina=min(mina,book[2][i]);
minb=min(minb,book[3][i]);
}
long long temp1=Quick_Power(2,mina);
long long temp2=Quick_Power(3,minb);
printf("%lld\n",(temp1*temp2)%mod);
}
return 0;
}
NO.4
题目描述:
NO.5
题目描述:
解题思路:给区间加上一个相等数列,最后单点查询设a[]是原数组,b[]是差分数组,考虑一次在[L,R]内加w对a数组的影响a[x]=a[x]+w(x∈[L,R])考虑a数组的变化对b数组的影响 (b[i]=a[i]-a[i-1])b[L]=b[L]+w;b[R+1]=b[R+1]-w;我们发现每次修改只会改变b数组2个位置的值sum数组存储到现在为止有几个点符合条件(前缀和)。
带注释的代码:
#include<bits/stdc++.h>
using namespace std;
long long a[80010],b[80010],sum[80010],ans,now;
long long n,opt,mod,minn,maxx,l,r,x,f;
char ch[5];
int main()
{
cin>>n>>pot>>mod>>minn>>maxx;
for(long long j=1;j<=opt;j++)
{
cin>>ch>>l>>r;
if(ch[0]=='A')
{
scanf("%lld",&x);
b[l]+=x;
b[r+1]-=x;
}
else
{
ans=0;now=0;
for(long long i=1;i<=r;i++)
{
now+=b[i];
if(i>=l&&(now*i)%mod>=minn&&(now*i)%mod<=maxx)
ans++;
}
cout<<ans<<endl;
}
}
cin>>f;
for(long long i=1;i<=n;i++)
{
a[i]=a[i-1]+b[i];
if((a[i]*i)%mod>=minn&&(a[i]*i)%mod<=maxx)sum[i]=sum[i-1]+1;
else sum[i]=sum[i-1];
}
for(long long j=1;j<=f;j++)
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",sum[r]-sum[l-1]);
}
return 0;
}