目录
Tokitsukaze and Average of Substring
求一个区域内的值(红色区域),需要知道两个点,减去图上虚线部分,再将多减去的加回来即可(+1是为了包含边缘点,可类比一维前缀和):
一维前缀和
【模板】前缀和
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=100000+10;
int n,q;
int a[N],s[N];
signed main()
{
cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i];
}
while(q--)
{
int l,r;cin>>l>>r;
cout<<s[r]-s[l-1]<<endl;
}
return 0;
}
Tokitsukaze and Average of Substring
详解:点击跳转
时间复杂度分析:n^2<1e7,1s大概是1e8,双层遍历不会超时
思路:分别预处理每个字符的前缀和,才可以用O(1)求出每个l-r中相同字符的对数
tips:
对于cnt,是要将所有数对相加,已知数对之间存在规律,即之间为一个等差数列,所有利用高斯求和公式(1+2+3+……+n=n*(n-1)/2)求出每一组l和r对应的所有数对
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5000+10;
int pre[30][N];//前缀和数组
void solve()
{
int n;cin>>n;
string s;cin>>s;
for(int i=1;i<=n;i++)//遍历每一个字符
{
//将每一个字符转换成数字,如将a映射成1、b映射成2……
int cnt=s[i-1]-'a'+1;
for(int j=1;j<=26;j++)//使用前缀和进行预处理
{
if(j==cnt)
pre[j][i]=pre[j][i-1]+1;
else
pre[j][i]=pre[j][i-1];
}
}
double ans=0.0;//初始化
for(int i=1;i<=n;i++)//对于每一个字符对进行遍历
{
for(int j=i+1;j<=n;j++)//对第i个字符的后面字符进行遍历
{
int l=i,r=j;//左右区间
double cnt=0;//cnt是每一个C函数的值,即一个区间的数量
//枚举26个字符l和r之间的数对数,在前面的预处理时已经过滤掉不相关的字符
for(int k=1;k<=26;k++)
{
int tmp=pre[k][r]-pre[k][l-1];//l~r区间之间的对数
//左边是对不同字符间的情况相加,右边是对每一个相同的字符进行求和
cnt+=tmp*(tmp-1)/2;
}
ans=max(ans,cnt/(double)(r-l+1));//取 f函数最值
}
}
printf("%.6f\n",ans);
//由于有多组测试数据,还要将pre数组置0
for(int i=1;i<=26;i++)
for(int j=1;j<=n;j++)
pre[i][j]=0;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--)//将每一个数据点用一个函数解决
solve();
return 0;
}
二维前缀和(在考场上一般是现推)
求二维前缀和公式(求增加点(i,j)时虚线上方的区域):
求一个区域内的值(红色区域),需要知道两个点,减去图上虚线部分,再将多减去的加回来即可(+1是为了包含边缘点,可类比一维前缀和):
【模板】二维前缀和
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1000+10;
int n,m,q;
int pre[N][N],g[N][N];
int ans;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>g[i][j];
//预处理前缀和
pre[i][j]=pre[i][j-1]+pre[i-1][j]-pre[i-1][j-1]+g[i][j];
}
}
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
ans=pre[x2][y2]-pre[x2][y1-1]-pre[x1-1][y2]+pre[x1-1][y1-1];
cout<<ans<<endl;
}
return 0;
}
[HNOI2003]激光炸弹
抽象出来就是要求求一个矩形内的一个正方形区域内的最大的值的和。
需要枚举矩形中正方形能圈住的每一个区域,前面预处理将5001所有都枚举了,后面再将所有的r正方形枚举一定不会错。
注意!!!一个方格能有多个目标,所以需要写成g+=而不是g=
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5000+10;
int g[N][N],pre[N][N];//g是单独的一个小正方形的值,pre是前缀和
int ans;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,r;
cin>>n>>r;
for(int i=1;i<=n;i++)
{
int x,y,v;cin>>x>>y>>v;
//由于坐标是从0开始,为了方便二位前缀和,将总体++使其从1开始,预处理时需要加1
x++;y++;
//一个方格能有多个目标,所以需要写成g+=而不是g=
g[x][y]+=v;
}
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++)//前缀和预处理
{
pre[i][j]=pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1]+g[i][j];
}
for(int i=r;i<=5001;i++)//前面的点从1开始,所有需要遍历到5001
{
for(int j=r;j<=5001;j++)
{
int x1=i-r+1,y1=j-r+1;
int x2=i,y2=j;//x1从1开始,x1与x2是一个对角线关系
int tmp=pre[x2][y2]-pre[x2][y1-1]-pre[x1-1][y2]+pre[x1-1][y1-1];
ans=max(ans,tmp);
}
}
cout<<ans<<endl;
return 0;
}