【NOIP2016普及组】洛谷2119 魔法阵的解析(数学分析+枚举优化)

题目:luogu2119.
题目大意:给定一个长度为 m m m的序列 x i x_i xi,要求求出满足 x a &lt; x b &lt; x c &lt; x d , x b − x a = 2 ( x d − x c ) x_a&lt;x_b&lt;x_c&lt;x_d,x_b-x_a=2(x_d-x_c) xa<xb<xc<xdxbxa=2(xdxc) x b − x a &lt; x c − x b 3 x_b-x_a&lt;\frac{x_c-x_b}{3} xbxa<3xcxb a , b , c , d a,b,c,d a,b,c,d的数量.
1 ≤ n = max ⁡ { x i } ≤ 1.5 ∗ 1 0 4 , 1 ≤ m ≤ 4 ∗ 1 0 4 1\leq n=\max \{x_i\}\leq 1.5*10^4,1\leq m\leq 4*10^4 1n=max{xi}1.5104,1m4104.

首先这道题可以直接用一个桶,先把所有数塞进这个桶里.

然后我们可以开始 O ( n 4 ) O(n^4) O(n4)枚举答案.

但我们发现 x d = x b − x a + 2 x c 2 x_d=\frac{x_b-x_a+2x_c}{2} xd=2xbxa+2xc,所以我们可以省去以为的枚举,做到 O ( n 3 ) O(n^3) O(n3)枚举,实测在洛谷上能拿到85分.

到这一步优化的代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=15000,M=40000;
int n,m,x[M+9];
LL num[N+9][4],cnt[N+9];
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=m;i++){
    scanf("%d",&x[i]); 
    cnt[x[i]]++;
  }
}
Abigail work(){
  for (int a=1;a<=n;a++)
    for (int b=a+1;b<=n;b++)
      for (int c=b+1;c<=n;c++){
        if (b-a&1||3*b-3*a>=c-b) continue;
        int d=b-a+c*2>>1;
        LL ans=cnt[a]*cnt[b]*cnt[c]*cnt[d];
        num[a][0]+=ans;num[b][1]+=ans;num[c][2]+=ans;num[d][3]+=ans;
      }
  for (int i=1;i<=n;i++)
    for (int j=0;j<4;j++)
      num[i][j]/=cnt[i]?cnt[i]:1;
}
Abigail outo(){
  for (int i=1;i<=m;i++){
    for (int j=0;j<3;j++)
      printf("%lld ",num[x[i]][j]);
    printf("%lld\n",num[x[i]][3]);
  }
}
int main(){
  into();
  work();
  outo();
  return 0;
}

接下来的优化需要将 x b − x a &lt; x c − x b 3 x_b-x_a&lt;\frac{x_c-x_b}{3} xbxa<3xcxb变式,改成 3 ( x b − x a ) &lt; x c − x b 3(x_b-x_a)&lt;x_c-x_b 3(xbxa)<xcxb.

然后我们设 t = x d − x c t=x_d-x_c t=xdxc,那么 2 t = x b − x a , 6 t &lt; x c − x b 2t=x_b-x_a,6t&lt;x_c-x_b 2t=xbxa,6t<xcxb.

然后我们在设 6 t + k = x c − x b 6t+k=x_c-x_b 6t+k=xcxb,那么就可以画一个图:
在这里插入图片描述
我们发现 x d − x a = 9 t + k x_d-x_a=9t+k xdxa=9t+k,由于所有数都是整数,所以 k k k的最小值为 1 1 1.

现在我们枚举 t t t x a x_a xa,我们就可以计算出 x b = x a + 2 t x_b=x_a+2t xb=xa+2t,然后 C C C的最小值为 x a + 2 t + 6 t + k m i n = x a + 8 t + 1 x_a+2t+6t+k_{min}=x_a+8t+1 xa+2t+6t+kmin=xa+8t+1 D D D的最小值为 x a + 8 t + 1 + t = x a + 9 t + 1 x_a+8t+1+t=x_a+9t+1 xa+8t+1+t=xa+9t+1.

我们发现若 t t t x a x_a xa确定了,那么我们就可以确定只有 k k k的问题了,我们发现 k k k [ 1 , n − 9 t ] [1,n-9t] [1,n9t]内的任意一个数都是可以的,那我们就可以维护一个前缀和数组 v i s [ i ] [ t ] vis[i][t] vis[i][t]表示 c c c i i i d d d i + t i+t i+t的时候有多少种.

由于这样会占用很大内存导致MLE,所以我们在推的时候处理,这样就可以确定 c c c d d d的数量了.但是要注意,由于我们要边处理边推,所以我们要逆推.

a a a b b b的确定和 c c c d d d很像,换成顺推就可以了.

而这样做的时间复杂度为 O ( n 2 ) O(n^2) O(n2),还是不能拿到满分.

接下来我们就进行一些细节的优化,我们发现 x a + 9 t + k &lt; = n x_a+9t+k&lt;=n xa+9t+k<=n,而 x a , k &gt; = 1 x_a,k&gt;=1 xa,k>=1,所以我们可以发现其实 t &lt; = n − 2 9 t&lt;=\frac{n-2}{9} t<=9n2,所以 t t t只需要枚举到 n − 2 9 \frac{n-2}{9} 9n2就可以了.但是这样写代码会有除法,比较慢,所以我们写成 9 t &lt; = n − 2 9t&lt;=n-2 9t<=n2.

那么这个算法的时间复杂度就是 O ( n 2 9 ) O(\frac{n^2}{9}) O(9n2),可以过了,可能需要一定的常数优化(然而并没有什么可以优化的地方).

所以大致的算法流程如下:
1.枚举 t = 1 → n 2 9 t=1\rightarrow \frac{n^2}{9} t=19n2.
2.枚举 D = n → 2 + 9 t D=n\rightarrow 2+9t D=n2+9t,可以推出 C = D − t C=D-t C=Dt,之后可以设 A A A A A A的最大值, B B B B B B的最大值,得到 B = C − 6 t − 1 , A = 6 t − 1 B=C-6t-1,A=6t-1 B=C6t1,A=6t1.
3.在2的同时,记录一个 s u m sum sum初始为 0 0 0,表示当前 D D D时, A A A B B B的情况有多少种.
4.同样,枚举 A = n − 9 t − 1 → 1 A=n-9t-1\rightarrow 1 A=n9t11.
5.同样,到这处理前缀和.

代码如下:

#include<bits/stdc++.h>
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=15000,M=40000;
int n,m,x[M+9],num[4][N+9],cnt[N+9];
Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=m;i++){
    scanf("%d",&x[i]); 
    cnt[x[i]]++;
  }
}
Abigail work(){
  int sum,A,B,C,D;
  for (int t=1;t*9<n;t++){
    sum=0;
    for (D=9*t-2;D<=n;D++){
      C=D-t;
      B=C-6*t-1;
      A=B-2*t;      //知道D,计算出A,B,C 
      sum+=cnt[A]*cnt[B];    //计算当前A和B的情况 
      num[2][C]+=cnt[D]*sum;    //num[2][C]+=cnt[A]*cnt[B]*cnt[C]
      num[3][D]+=cnt[C]*sum;    //num[3][D]+=cnt[A]*cnt[B]*cnt[D]
    }
    sum=0;
    for (A=n-9*t-1;A;A--){
      B=A+2*t;
      C=B+6*t+1;
      D=C+t;      //知道A,计算出B,C,D
      sum+=cnt[C]*cnt[D];    //计算当前C和D的情况
      num[0][A]+=cnt[B]*sum;    //num[0][A]+=cnt[B]*cnt[C]*cnt[D]
      num[1][B]+=cnt[A]*sum;    //num[1][B]+=cnt[A]*cnt[C]*cnt[D] 
    } 
  }
}
Abigail outo(){
  for (int i=1;i<=m;i++){
    for (int j=0;j<3;j++)
      printf("%d ",num[j][x[i]]);
    printf("%d\n",num[3][x[i]]);
  }
}
int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值