文章目录
前缀和与递推及练习题
前导知识
:
参考如下博客:
1. 10471:数列游戏 I(差分数组+前缀和)
ZZUOJ 10471
#include <iostream>
using namespace std;
#define int long long
const int mod = 1000000007;
const int N = 1000005;
int aa[N],la[N],ra[N],c[N],lb[N],rb[N];
signed main(){
int t;
cin>>t;
while(t--){
int n,a,b;
cin>>n>>a>>b;
for(int i=1;i<=n;i++){
aa[i]=0;
}
aa[0]=0;
for(int i=1;i<=a;i++){
cin>>la[i]>>ra[i]>>c[i];
for(int j=la[i];j<=ra[i];j++){
aa[j]+=c[i] % mod;
}
}
for(int j=1;j<=n;j++){
aa[j]+=aa[j-1] % mod;
}
for(int i=1;i<=b;i++){
cin>>lb[i]>>rb[i];
cout<<aa[rb[i]] % mod - aa[lb[i]-1] % mod<<endl;
}
}
return 0;
}
差分数列,如有个数组a, 差分数列为d, d[i]=a[i]-a[i-1],因此改变某段区间的值时改变首尾元素的值即可。
2. 扫雷游戏简化版(递推+动态规划)
a [ i − 1 ] = d p [ i ] + d p [ i − 1 ] + d p [ i − 2 ] a[i-1]=dp[i]+dp[i-1]+dp[i-2] a[i−1]=dp[i]+dp[i−1]+dp[i−2]
其中 d p dp dp为第一列i号位置有几颗雷, a a a为第二列第 i i i号位置的数字.
#include <cstdio>
using namespace std;
const int N = 100005;
int a[N],dp[N],n;
bool check(){
for(int i=2;i<=n;i++){
dp[i]=a[i-1]-dp[i-1]-dp[i-2];
if(dp[i]<0)
return false;
}
return dp[n-1]+dp[n]==a[n];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int t=0;
if(check()){
t++;
}
dp[1]=1; /* 重新开始第二种情况 第一个位置有雷 */
if(check())
t++;
printf("%d\n",t);
return 0;
}
3. 扫雷游戏(枚举+搜索)
#include <iostream>
#include <cstdio>
using namespace std;
const int dx[8] = {1,-1,1,1,-1,-1,0,0},
dy[8] = {0,0,1,-1,1,-1,-1,1};/* 八个方向 */
int n,m;
char a[110][110];
bool isValid(int x,int y)
{
if (x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == '*')/* 判断当前搜索是否越界,且当前枚举点为“?” */
return true;
return false;
}
int Search(int x,int y)/* 按照八个方向搜索 */
{
int ans = 0;
for (int i=0;i<8;++i)
{
int new_x= x + dx[i];
int new_y= y + dy[i];
if (isValid(new_x,new_y))
ans++;/* 累加 */
}
return ans;/* 返回答案 */
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for (int j=1;j<=m;++j)
cin >> a[i][j];
/* 读入 */
for (int i=1;i<=n;++i)
for (int j=1;j<=m;++j)
{
if (a[i][j] == '*')
cout << '*';
if (a[i][j] == '?')
cout << Search(i,j);/* 输出 */
if (j== m)
cout << endl;/* 换行 */
}
return 0;
}
4. 因数和(递推+线性筛)
要记住的点:
- 因子和 σ ( n ) = ∑ d ∣ n d σ(n)=\sum_{d|n} d σ(n)=∑d∣nd
- x ( m o d y ) = x − [ x y ] × y x (mod\ y)=x-[\frac{x}{y}] \times y x(mod y)=x−[yx]×y
预处理 σ ( i ) σ(i) σ(i):
每个因数的对哪些数有贡献 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
// 暴力筛O(nlogn)
ll f[N],s;
int n;
void init(int n){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j+=i)
f[j]+=i;
}
}
公式推导原理
:
f
(
1
)
=
1
;
f
(
2
)
=
1
+
2
;
f
(
3
)
=
1
+
3
;
f
(
4
)
=
1
+
2
+
4
;
f
(
5
)
=
1
+
5
;
.
.
.
f(1)=1;\\f(2)=1+2;\\f(3)=1+3;\\f(4)=1+2+4;\\f(5)=1+5;\\...
f(1)=1;f(2)=1+2;f(3)=1+3;f(4)=1+2+4;f(5)=1+5;...
线性筛 时间复杂度: O ( n ) O(n) O(n)
//线筛O(n)
ll f[N],s,g[N];
int p[N],cnt,vis[N];
int n;
void init(int n){
vis[0]=vis[1]=1;
f[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) p[++cnt]=i,f[i]=g[i]=i+1;
for(int j=1;j<=cnt&&i*p[j]<=n;j++){
vis[i*p[j]]=1;
if(i%p[j]==0){
g[i*p[j]]=g[i]*p[j]+1;
f[i*p[j]]=f[i]/g[i]*g[i*p[j]];
break;
}
f[i*p[j]]=f[i]*f[p[j]];
g[i*p[j]]=g[p[j]];
}
}
}
f [ i ] f[i] f[i]即为所求 σ ( i ) σ(i) σ(i)
完整程序
:
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
/* 暴力筛O(nlogn) */
/*
ll f[N],s;
int n;
void init(int n){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j+=i)
f[j]+=i;
}
}
*/
/* 线筛O(n) */
ll ans[N];
ll f[N],s,g[N];
ll p[N],cnt,vis[N];
ll n;
void init(ll n){
vis[0]=vis[1]=1;
f[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) p[++cnt]=i,f[i]=g[i]=i+1;
for(int j=1;j<=cnt&&i*p[j]<=n;j++){
vis[i*p[j]]=1;
if(i%p[j]==0){
g[i*p[j]]=g[i]*p[j]+1;
f[i*p[j]]=f[i]/g[i]*g[i*p[j]];
break;
}
f[i*p[j]]=f[i]*f[p[j]];
g[i*p[j]]=g[p[j]];
}
}
}
int main(){
scanf("%lld",&n);
init(n);
for(int i=1;i<=n;i++){
ans[i]=ans[i-1]+n-f[i];
printf("%lld ",ans[i]);
}
return 0;
}
5. 牛妹吃豆子(二维前缀和)
#include <cstdio>
using namespace std;
typedef long long ll;
const int N=2e3+5;
ll f[N][N]={0};
int main()
{
int n,m,k,q,x1,y1,x2,y2;
scanf("%d%d%d%d",&n,&m,&k,&q);
while(k--){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
f[x1][y1]++;
f[x2+1][y2+1]++;
f[x2+1][y1]--;
f[x1][y2+1]--;
}
/* 做差分 */
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
}
while(q--){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%lld\n",f[x1-1][y1-1]+f[x2][y2]-f[x1-1][y2]-f[x2][y1-1]);
}
return 0;
}
注意数组应开一个long long 型的,保证输出数据量
6. 数楼梯(高精度+斐波那契数列)
#include <cstdio>
using namespace std;
const int N = 5005;
int n;
int f[N][N];
int l=1;
void add(int k){
/* 高精度加法,k来存阶数 */
for(int i=1;i<=l;i++){
f[k][i]=f[k-1][i]+f[k-2][i]; /* 斐波那契公式计算 */
}
for(int i=1;i<=l;i++){
if(f[k][i]>=10){
f[k][i+1]+=f[k][i]/10; /* 进位,保证存储更多的数据 */
f[k][i]=f[k][i]%10;
if(f[k][l+1]){ /* 长度增加,如果下一位不为0 */
l++;
}
}
}
}
int main(){
scanf("%d",&n);
f[1][1]=1;
f[2][1]=2;
for(int i=3;i<=n;i++){
add(i);
}
for(int i=l;i>=1;i--)
printf("%d",f[n][i]);
return 0;
}
7. 最大正方形(前缀和+动态规划)
题解
:
以 ( i , j ) (i,j) (i,j)为右下角结点,当 a [ i ] [ j ] = = 1 a[i][j]==1 a[i][j]==1时,检查左、上、左上角的结点是否为0,以 f [ i ] [ j ] f[i][j] f[i][j]来表示以 ( i , j ) (i,j) (i,j)为结点的最大正方形的边长
dp做法
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 105;
int a[N][N];
int dp[N][N];
int n,m;
int ans=0;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
if(a[i][j]==1){
dp[i][j]=min(min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;
}
ans=max(ans,dp[i][j]);
}
}
printf("%d",ans);
return 0;
}
二维前缀和+二分解释
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 105;
int a[N][N];
int n,m;
int ans=0;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int l=0,r=min(n,m);
while(l<=r){ /* 引入二分节省时间 */
int mid=(l+r)>>1;
if(i+mid>n||j+mid>m||a[i+mid][j+mid]-a[i+mid][j]-a[i][j+mid]+a[i][j]<mid*mid)
r=mid-1;
else
l=mid+1;
}
if(a[i+r][j+r]-a[i+r][j]-a[i][j+r]+a[i][j]==r*r){
ans=max(ans,r);
}
}
}
printf("%d",ans);
return 0;
}
8. 领地选择(二维前缀和)
#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int N = 3e3;
int n,m,c;
int a[N][N];
signed main(){
scanf("%lld%lld%lld",&n,&m,&c);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&a[i][j]);
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
}
}
int ans=-0x7fffffff;
int ii=1,jj=1;
for(int i=c;i<=n;i++){
for(int j=c;j<=m;j++){
int t=a[i][j]-a[i-c][j]-a[i][j-c]+a[i-c][j-c];
if(t>ans){
ii=i-c+1;
jj=j-c+1;
ans=t;
}
}
}
printf("%lld %lld",ii,jj);
return 0;
}
9. Foehn Phenomena(差分)
差分板子题
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[4000005];
ll n,q,s,t;
/* 速读 */
inline ll read(){
char c=getchar();
int f=1;
ll x=0;
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x*f;
}
/* 温度变化计算 */
ll temp(ll x){
if(x>=0)
return -x*s;
else return -x*t;
}
int main(){
ll ans=0;
n=read();q=read();s=read();t=read();
ll last=0;
for(register int i=0;i<=n;i++){
ll k=read();
a[i]=k-last;
ans+=temp(a[i]);
last=k;
}
/* 差分 */
for(register int i=1;i<=q;i++){
ll l=read(),r=read(),k=read();
ans-=temp(a[l]),a[l]+=k,ans+=temp(a[l]);
/* 借用数学思想的处理方法 */
if(r!=n) ans-=temp(a[r+1]),a[r+1]-=k,ans+=temp(a[r+1]);
printf("%lld\n",ans);
}
return 0;
}
这个
register int
中的register表示使用cpu内部寄存器(寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件)的变量,而平时的int是把变量放在内存中,存到寄存器中可以加快变量的读写速度
速读
inline ll read(){
char c=getchar();
int f=1;
ll x=0;
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x*f;
}
10. 无聊的数列(线段树+差分)
没有算法本质的做法
#include <cstdio>
#define int long long
const int N = 1e5 + 5;
int n,m;
int a[N];
int opt;
int l,r,K,D;
int p;
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
while(m--){
scanf("%d",&opt);
if(opt==1){
scanf("%d%d%d%d",&l,&r,&K,&D);
for(int i=l;i<=r;i++){
a[i]+=K+(i-l)*D;
}
}else if(opt==2){
scanf("%d",&p);
printf("%d\n",a[p]);
}
}
return 0;
}
线段树做法
#include<iostream>
#include<cstdio>
#define ls root<<1,l,mid
#define rs root<<1|1,mid+1,r
#define MAXN 100001
using namespace std;
int n,m;
int s[MAXN],sum[MAXN<<2],lazy[MAXN<<2];
inline void push_up(int root)
{
sum[root]=sum[root<<1]+sum[root<<1|1];
}
inline void push_down(int root,int len)
{
if(lazy[root])
{
lazy[root<<1]+=lazy[root];
lazy[root<<1|1]+=lazy[root];
sum[root<<1]+=(len-(len>>1))*lazy[root];
sum[root<<1|1]+=(len>>1)*lazy[root];
lazy[root]=0;
}
}
inline void update(int root,int l,int r,int L,int R,int val)
{
if(L<=l && r<=R){sum[root]+=(r-l+1)*val;lazy[root]+=val;return;}
push_down(root,r-l+1);
int mid=(l+r)>>1;
if(mid>=L) update(ls,L,R,val);
if(mid<R) update(rs,L,R,val);
push_up(root);
}
inline int query(int root,int l,int r,int L,int R)
{
if(L<=l && r<=R) return sum[root];
push_down(root,r-l+1);
int mid=(l+r)>>1;int total=0;
if(mid>=L) total+=query(ls,L,R);
if(mid<R) total+=query(rs,L,R);
return total;
}
int main()
{
in(n),in(m);
for(int i=1;i<=n;i++) in(s[i]);
int type,L,R,K,D,ask;
while(m--)
{
in(type);
if(type==1)
{
in(L),in(R),in(K),in(D);
update(1,1,n,L,L,K);
if(R>L) update(1,1,n,L+1,R,D);
int N=R-L+1;
if(R!=n) update(1,1,n,R+1,R+1,-(K+(N-1)*D));
}
else
{
in(ask);
printf("%d\n",s[ask]+query(1,1,n,1,ask));
}
}
return 0;
}