2020.2.15 LGR69 洛谷 2 月月赛 2 div.1题解报告


2020.2.15 LGR69 洛谷 2 月月赛 2 div.1题解报告

\[\text{单挑狂猎之王的男人从二楼掉下来摔死了}\]
\[\text{By:The Block Withcer}\]

答题情况:

  • 总成绩 : 139 , 排名 : 50 / 223
  • T1 : 100 T2 : 0 T3 : 29 T4 : 10

各题目分析:

  • 题目 1 :
    预估成绩 : 100 实际成绩 : 100 考试用时 : 2:00 ~ 3:30, 5 : 30 ~ 5 : 50

    一开始以为是模拟, 之后发现是个比较zz的DP.
    之后就开始写, 过不了样例一直调, 用了很长时间.
    之后过了样例只有40, 心态崩了, 先去写暴力.

    之后发现是unsigned long long的输出问题, 最后改了过来.

  • 题目 2 :
    预估成绩 : 3 实际成绩 : 考试用时 : 3 : 30 ~ 3 : 45

    比较难搞的一道题, 题面比较长, 难理解.
    做T1心态有些崩, 考场上连暴力也没想出来.

    • 教训: 做好时间规划, 保持良好心态.
      除非稳抓稳打, 不要在一个半生半会题上浪费太多时间
  • 题目 3 :
    预估成绩 : 29 实际成绩 : 29 考试用时 : 3 : 50 ~ 4 : 30

    觉得比较可做, 先写了暴力29分.
    之后觉得 可以用平衡树 维护一下, 刚了一波没刚出来.

  • 题目 4 :
    预估成绩 : 29 实际成绩 : 10 考试用时 : 4 : 35 ~ 5 : 20

    发现 用二维线段树 会比较方便, 于是先写了个二维线段树
    一直调不出来, 这时候T1还只有40, 决定写暴力 刚T1.
    暴力复杂度有问题, 只有10分.


题目解析:

T1 :

题目给出了详细的转移方式, 显然可以进行递推
维护长度为i的 1语句, 2程序片段, 3语句块, 4函数, 5值的数量.
暴力转移即可.

由于数据范围较小, 且默认开启O2, n^2可过.
注意输出 unsigned long long时使用cout


T2 :

最后那个相同的数只可能是 \(m = \max_{i=1}^{n} a_i\)或者 \(\ge m\) 的最小质数.
每次算两种的答案然后取 \(\max\) 即可.

算答案的时候, 将每个数加到目标值的过程按照质数划分成若干段, 可以差分实现.
对于长度为 k 的一段,选择用c1还是 c2 取决于 kc1 和 c2 哪个大.
显然这玩意儿是单调的,那么前缀和一下即可.

注意如果目标值是 \(m\) 且不为质数, 每个数加到\(m\) 的最后一段可能只能使用 c1.
时间复杂度\(O(n+m+q)\).


T3 :

问题即,给出一个点集,支持插入删除,和查询max(x+y),使得x+y < C
再继续分析一下,发现如果x,y < C/2,这个也是平凡的
x,y < C/2可以推出 x+y < C,所以选两个最大的C/2以内的数就可以覆盖这部分的贡献

目前非平凡的部分在于从[0,C/2)中选一个数x,[C/2,C)中选一个数y,x+y的贡献
x+y<C 等价于 x<C-y
我们可以认为是在最小化C-x-y
于是用一棵平衡树维护,这里平衡树这个结构是用来满足x<C-y这个条件的
每个在[0,C/2)中的x就直接插入,每个在[C/2,C)中的y,就变成C-y然后插入
平衡树需要维护子树内最小的C-y,最大的x,最小的C-x-Y

具体一点来说,所有[0,C/2)中的数x看做A集合,所有[C/2,C)中的数y变成C-y后看做B集合
我们要在A和B集合中选出两个数a,b,使得:
a<b,b-a最大

对每个A集合中的x,维护出其在B集合中的后继C-y
对每个B集合中的C-y,维护出其在A集合中的前驱x
这样一定是最优的,
每次修改这个前驱后继变动是O(1)的
这样就可以不用手写平衡树,只需要stl的set就可以了

总时间复杂度O( nlogn )


T4 :

考虑对平面进行分治,选一维x维进行分治
每次分治的时候,处理跨过分治中线的询问,这样询问就变成一个x维前缀,y区间合并的形式
分治的意义是拆出两个前缀,
因为这个信息不具有可减性

然后扫描线往两边分别扫,每次x维扫到新加入一个矩形的时候就进行一次y维上的区间加,每次x维扫出一个矩形的时候就进行一次y维上的区间减

我们需要维护的信息是y维的一个区间,在x维的一个前缀,上的信息合并,这道题中维护一个历史最大值就可以了,因为分治导致x维选的是一个前缀
每个矩形被拆成2=O(1)个查询
所以查询复杂度是O(qlogn)的

关于历史最大值的维护
区间加,维护区间历史最大值,维护二元组信息(a,ha)表示区间被加了a,历史最大值是ha
(a,ha)*(b,hb)=(a+b,max(ha,hb+a))

考虑如何让分治的复杂度正确
可以和莫队类似,试着利用之前的信息, 撤回一些修改

当撤回到虚线指的地方的时候,需要高效撤销之前算出来的区间历史最大值,因为我们虚线指的地方是一个分治中线
也就是说我们每次撤回到一个扫描线开始扫的位置,此时每个位置的最大值是跨过这个分治中线的矩形的最大值的合并
即我们要清空历史最大值,让其变成目前的最大值,这个可以通过在线段树上打一个特殊的标记来维护,即tag表示区间是否把历史最大值变成当前的最大值

可以证明刚才这个构造中,总的扫过的距离是O( nlogn )的,而且每个矩形修改只会对应O( logn )次线段树上的区间修改
打特殊的标记总共O( n )次
每个矩形询问被拆成了O( 1 )次线段树上的区间历史最大值

总时间复杂度O( (n+m)log^2n + qlogn )
如果n,m,q同阶,可以将分治改成O( logn/loglogn )叉的分治结构,这样的多叉平衡可以将复杂度平衡为O( nlog^2n/loglogn )


代码实现:

T1 :

  • 考场代码 (正解):
//知识点: DP 
/*
By:Luckyblock
维护长度为i的 1语句, 2程序片段, 3语句块, 4函数, 5值的数量.
暴力DP转移. 
*/
#include <cstdio>
#include <iostream>
#include <ctype.h>
#define ull unsigned long long
const int MARX = 1e4 + 10;
//=============================================================
ull N, cnt[6][MARX]; 
//=============================================================
inline ull read()
{
    ull s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
    return s * w;
}
//=============================================================
int main()
{
    N = read(); cnt[2][0] = cnt[1][1] = 1;
    for(int i = 1; i <= N; i ++)
    {
      cnt[3][i] += cnt[2][i - 2];
      cnt[1][i] += cnt[3][i];
      cnt[4][i] += cnt[3][i - 2] + cnt[3][i - 4] + cnt[4][i - 2];
      cnt[5][i] += cnt[4][i] + cnt[5][i - 2];
      cnt[1][i] += cnt[5][i - 1];
      
      for(int j = 0; j < i; j ++) cnt[2][i] += cnt[2][j] * cnt[1][i - j]; 
    } 
    std :: cout << cnt[2][N];
}

T2:

  • 正解 :
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+5,M=1e7+20;
inline int readint(){
    int d=0,c=getchar();
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);c=getchar())d=d*10+(c^'0');
    return d;
}
int n,q,T,a[N],pri[M/10],tot,nxt[M],m;
LL ans;
bool vis[M];
struct Worker{
    int mx;
    int len_1,ct[M/10];
    LL prL[205],pr[205];
    void insert(int a){
        int l=nxt[a-1],r=nxt[mx]-1;
        if(l>r){
            len_1+=mx-a;
            return;
        }
        len_1+=mx-pri[r];
        ++pr[pri[l]-a];
        ++ct[l+1],--ct[r+1];
    }
    void init(){
        for(int i=1;i<=tot;++i){
            ct[i]+=ct[i-1];
            pr[pri[i]-pri[i-1]]+=ct[i];
        }
        for(int i=1;i<=154;++i)prL[i]=prL[i-1]+(LL)pr[i]*i,pr[i]+=pr[i-1];
        
    }
    inline LL solve(int c1,int c2){
        int x=min(c2/c1,154);
        return(len_1+prL[x])*c1+(pr[154]-pr[x])*c2;
    }
}_1,_2;
void sieve(int n){
    for(int i=2;i<=n;++i){
        if(!vis[i])pri[++tot]=i;
        for(int j=1;j<=tot&&i*pri[j]<=n;++j){
            vis[i*pri[j]]=1;
            if(i%pri[j]==0)break;
        }
    }
    int pre=0;
    for(int i=1;i<=tot;pre=pri[i++])
    for(int j=pre;j<pri[i];++j)nxt[j]=i;nxt[n]=tot+1;
}
int main(){
    n=readint(),q=readint(),T=readint();
    sieve(10000019);
    for(int i=1;i<=n;++i)a[i]=readint();
    m=*max_element(a+1,a+n+1);
    _1.mx=m,_2.mx=pri[nxt[m]];
    for(int i=1;i<=n;++i)
    _1.insert(a[i]),_2.insert(a[i]);
    _1.init(),_2.init();
    while(q--){
        ans&=131071;ans*=T;
        int c1=readint()^ans,c2=readint()^ans;
        printf("%lld\n",ans=min(1000000000000000000LL, min(_1.solve(c1,c2),_2.solve(c1,c2))));
    }
    return 0;
}

T3:

  • 考场代码:
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <vector>
#include <ctype.h>
#define min std::min
#define max std::max
#define ll long long
//=============================================================
int N, C;
std :: vector <int> S;
//=============================================================
inline int read()
{
    int s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
    return s * w;
}
//=============================================================
int main()
{
    N = read(), C = read();
    for(int i = 1; i <= N; i ++)
    {
      int opt = read(), x = read();
      if(opt == 1) S.push_back((x % C));
      else 
      {
        int Target = (x % C);
        for(int j = 0, size = S.size(); j < size; j ++) 
          if(S[j] == Target) S[j] = - 1;
      }
      if(S.size() < 2) {printf("EE\n"); continue;}
      int Ans = 0;
      for(int i = 0, size = S.size(); i < size; i ++)
        if(S[i] != - 1) for(int j = i + 1; j < size; j ++) 
          if(S[j] != - 1) Ans = max(Ans, (S[i] + S[j])% C);
      printf("%d\n", Ans);
    }
    return 0;
}
  • 正解:

T4:

  • 考场代码:
//
/*
By:Luckyblock
*/
#include <cstdio>
#include <algorithm>
#include <ctype.h>
#define min std::min
#define max std::max
#define int long long
const int MARX = 2010;
//=============================================================
int N, M, Q;
int Matrix[MARX][MARX]; 
//=============================================================
inline int read()
{
    int s = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
    return s * w;
}
//=============================================================
signed main()
{
    N = read(), M = read(), Q = read();
    for(int i = 1; i <= M; i ++)
    {
      int l1 = read(), l2 = read(), r1 = read(), r2 = read(), x = read();
      for(int j = l1; j <= r1; j ++)
        for(int k = l2; k <= r2; k ++)
          Matrix[j][k] += x;
    }
    for(int i = 1; i <= Q; i ++)
    {
      int l1 = read(), l2 = read(), r1 = read(), r2 = read(), Ans = 0;
      for(int j = l1; j <= r1; j ++)
        for(int k = l2; k <= r2; k ++)
          Ans = max(Ans, Matrix[j][k]);
      printf("%lld\n", Ans);
    }
    return 0;
}
  • 正解:

猜你喜欢

转载自www.cnblogs.com/luckyblock/p/12358124.html
今日推荐