zoukankan      html  css  js  c++  java
  • 【模拟赛】纪中提高A组 19.8.5 测试

    Task 1. 矩阵游戏


    题目大意:有一个 n 行 m 列的矩阵,第一行的数字为 1 2 3 ... m-1 m,第二行为 m+1 m+2 m+3 ...2m-1 2m,依次类推,第 i 行为 (i-1)m+1 (i-1)m+2 (i-1)m+3 ... (i-1)m+m-1 (i-1)m+m。现在有 k 次操作,每次操作对这个矩阵的第 x 行或者第 x 列的所有数字乘上 c ,求所有操作之后矩阵中数字的和 (mod 109+7)。

    数据范围:1≤ N,M ≤106,1≤ K ≤105,时限 1s。

    很明显,操作的顺序是不重要的,所以我们把对行的操作和对列的操作分开处理。

    设 ri 是对第 i 行乘的数,si 是对第 i 列乘的数,所有的 ri si 在开始时都为 1。

    一种 80 分暴力的思路:

    先处理所有关于行的操作,直接算出所有行不考虑关于列的操作的结果,再加上所有列的和乘上 si-1 的结果,这样对于一个位置 (x,y) 在第一次的结果中会被算 rx 次,在第二次的结果中会被计算 sy-1 次。这样的位置会被算 (rx+sy-1) 次,要么不被操作影响只算 1 次(rx=1,sy=1),要么被一个操作影响(rx=c,sy=1或rx=1,sy=c),要么被两个操作影响(rx=c,sy=d)。这样的交点的贡献应该是 rx*sy ,直接减掉前面多算的再加上实际的贡献,交点数量至多 K2 个。复杂度 O(N+M+K2)。

    代码:挂掉了,没调出来。

    正解:

    80分做法的复杂度瓶颈在于对交点的枚举,是否存在一种交点的有效枚举方法或者可以不考虑交点的做法?

    通过观察矩阵,我们发现如果没有列操作,每一列的和组成了一个等差数列(想象我们把矩阵拍扁了)。如果这时候再加上列操作,我们就可以直接对这个数列的某一项直接修改了。

    维护行的首项和公差的前缀和,这二者即上述等差数列的首项和公差,枚举每一项即可。复杂度 O(N+M)。

    代码:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 template<class T>void read(T &x){
     7     x=0; char c=getchar();
     8     while(c<'0'||'9'<c)c=getchar();
     9     while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
    10 }
    11 typedef long long ll;
    12 const int N=1000050;
    13 const int M=1000000007;
    14 ll mul(int x,int y){ return (1ll*x*y)%M;}
    15 
    16 int n,m,k;
    17 int r[N],s[N];
    18 int a,d,ans;
    19 int main(){
    20 //    freopen("game.in","r",stdin);
    21 //    freopen("game.out","w",stdout);
    22     read(n); read(m); read(k);
    23     for(int i=max(n,m);i;i--)r[i]=s[i]=1;
    24     char op[3]; int x,c;
    25     for(int i=1;i<=k;i++){
    26         scanf("%s",op);
    27         read(x); read(c);
    28         if(op[0]=='R') r[x]=mul(r[x],c);
    29         else s[x]=mul(s[x],c);
    30     }
    31     for(int i=1;i<=n;i++){
    32         a=(1ll*a+mul((1ll*(i-1)*m+1)%M,r[i]))%M;
    33         d=(d+r[i])%M;
    34     }
    35     for(int i=1;i<=m;i++){
    36         ans=(1ll*ans+mul(a,s[i]))%M;
    37         a=(a+d)%M;
    38     }
    39     printf("%d\n",ans);
    40     return 0;
    41 }
    ~qwq~

    PS:被 1LL 教做人了。

    Task 2. 跳房子


    题目大意:

    数据范围:

    代码:

    ???

    Task 3. 优美序列


    题目大意:给出一个 n 的排列 x,定义一个区间在排序后如果是一段连续的序列它就是优美的。给出 m 次询问,每次询问给定一个区间 [L,R],求包含区间 [L,R] 的最短优美区间。

    数据范围:1≤ N,M ≤105,数据随机,时限 1s。

    容易想到一个优美区间的性质:如果区间 [a,b] 是优美区间,那么区间 [a,b] 中的 xmax - xmin = pxmax 到 pxmin 之间的数字在区间 [a,b] 中的出现次数。

    根据这条性质,我们得到了一个初步的思路:维护区间最值,维护区间数字的出现个数。使用 ST 表 + 树状数组 我们便可以在 O(N2log N) 的时间内找到所有优美序列,在查询是使用二分或者保存所有优美序列直接遍历查找可以通过 50% 的测试点。

    代码:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<vector>
     5 using namespace std;
     6 
     7 template<class T>void read(T &x){
     8     x=0; char c=getchar();
     9     while(c<'0'||'9'<c)c=getchar();
    10     while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
    11 }
    12 const int N=5050;
    13 int n,m;
    14 int a[N],mx[N][N],mn[N][N];
    15 struct bitt{
    16     int c[N];
    17     int lowbit(int x){ return x&(-x);}
    18     void add(int x,int d){if(!x)return ; while(x<=n){ c[x]+=d; x+=lowbit(x);}}
    19     int query(int x){if(!x)return 0; int ret=0; while(x){ret+=c[x]; x-=lowbit(x);} return ret;}
    20 }t;
    21 bool is[N][N];
    22 struct answer{int l,r;bool v;}res[N][N];
    23 struct seq{int l,r;};
    24 vector<seq>b;
    25 void solve(){
    26     for(int i=1;i<=n;i++){
    27         read(a[i]); mx[i][i]=mn[i][i]=a[i];
    28     }
    29     for(int i=1;i<=n;i++)
    30         for(int j=i+1;j<=n;j++){
    31             mx[i][j]=max(mx[i][j-1],a[j]);
    32             mn[i][j]=min(mn[i][j-1],a[j]);
    33         }
    34     for(int i=1;i<=n;i++){
    35         t.add(a[i-1],-1);
    36         t.add(a[i],1);
    37         for(int j=i+1;j<=n;j++){
    38             t.add(a[j],1);
    39             if((mx[i][j]-mn[i][j])==(j-i)) is[i][j]=1;
    40         }
    41     }
    42     for(int len=0;len<n;len++){
    43         for(int i=1,j;i<=n;i++){
    44             j=i+len;
    45             if(is[i][j]) b.push_back((seq){i,j});
    46         }
    47     }
    48     read(m);
    49     for(int i=1,l,r;i<=m;i++){
    50         read(l); read(r);
    51         if(l==r||is[l][r]){ printf("%d %d\n",l,r); continue;}
    52         if(res[l][r].v){ printf("%d %d\n",res[l][r].l,res[l][r].r); continue;}
    53         for(int j=0;j<b.size();j++){
    54             if(b[j].l<=l&&r<=b[j].r){
    55                 printf("%d %d\n",b[j].l,b[j].r);
    56                 res[l][r]=(answer){b[j].l,b[j].r,1};
    57                 break;
    58             }
    59         }
    60     }
    61 }
    62 int main(){
    63 //    freopen("sequence.in","r",stdin);
    64 //    freopen("sequence.out","w",stdout);
    65     read(n);
    66     if(n*n<=25000000){solve(); return 0;}
    67     return 0;
    68 }
    ~qwq~

    我们可以继续思考优美区间还具有哪些性质。如果记 x 在排列中出现位置为 p,若区间 [a,b] 是一个优美区间,那么 [a,b] 中的 xmax  xmin 在 p 中出现的位置 pxmax 和 pxmin 之间也存在两个最值,最后的答案区间两端点一定包含这两个位置;再将这两个位置作为当前区间的左右端点扩展 ... 直到无法扩展为止,我们就找到了答案区间。按照这个思路,我们可以 ST表 维护 x 和 p 数组的区间最值,每次模拟这个扩展端点的过程,复杂度约为 O(N2)(数据随机,几乎跑不满)。不卡常可以通过 80% 的测试点,卡常之后达到 90% 的测试点(无能为力了)。但是在考场上有人不卡常以一个极小的常数通过本题。

    代码:(读优输优且开 O3 大样例跑 1.7s

     1 //#pragma GCC optimize(3,"Ofast","inline")
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<algorithm>
     5 using namespace std;
     6 
     7 template<class T>void read(T &x){
     8     x=0; char c=getchar();
     9     while(c<'0'||'9'<c)c=getchar();
    10     while('0'<=c&&c<='9'){x=(x*10)+(c-48); c=getchar();}
    11 }
    12 void write(int x){
    13     if(x<0)putchar('-'),x=-x;
    14     if(x>9)write(x/10); putchar(x%10+'0');
    15 }
    16 const int N=100050;
    17 
    18 int a[N],p[N],log[N],n;
    19 struct ST{
    20     int mx[N][18],mn[N][18];
    21     void init(int _[]){
    22         for(register int i=1;i<=n;i++) mx[i][0]=mn[i][0]=_[i];
    23         for(register int j=1,len=1;j<=log[n];j++,len<<=1)
    24             for(register int i=1;i<=n-len-len+1;i++)
    25                 mx[i][j]=max(mx[i][j-1],mx[i+len][j-1]),
    26                 mn[i][j]=min(mn[i][j-1],mn[i+len][j-1]);
    27     }
    28     inline int qmx(int l,int r){
    29         int k=log[r-l+1];
    30         return max(mx[l][k],mx[r-(1<<k)+1][k]);
    31     }
    32     inline int qmn(int l,int r){
    33         int k=log[r-l+1];
    34         return min(mn[l][k],mn[r-(1<<k)+1][k]);
    35     }
    36 }A,P;
    37 int main(){
    38 //    freopen("sequence.in","r",stdin);
    39 //    freopen("sequence.out","w",stdout);
    40     read(n); read(a[1]); p[a[1]]=1;
    41     for(register int i=2;i<=n;i++)
    42         read(a[i]), p[a[i]]=i, log[i]=log[i>>1]+1;
    43     A.init(a); P.init(p); int m,L,R,al,ar,l,r,t1,t2; read(m);
    44     for(register int i=1;i<=m;i++){
    45         read(L); read(R);
    46         if(L==R){ write(L); putchar(' '); write(R); putchar('\n'); continue;}
    47         al=A.qmn(L,R); ar=A.qmx(L,R);
    48         l=P.qmn(al,ar); r=P.qmx(al,ar);
    49         while(1){
    50             al=A.qmn(l,r); ar=A.qmx(l,r);
    51             t1=P.qmn(al,ar); t2=P.qmx(al,ar);
    52             if(l==t1) if(r==t2) break;
    53             l=t1; r=t2;
    54         }
    55         write(l); putchar(' '); write(r); putchar('\n');
    56     }
    57     return 0;
    58 }
    ~qwq~

    上面两个性质我们想不到什么更好的做法了,我们再去找找其他的性质。

    根据 https://blog.csdn.net/qq_16267919/article/details/83089934 :

    优美区间 [a,b] 中一定存在 b-a 对相差为 1 的数对,记为 cnt。当一段区间 cnt=b-a 时,它是优美区间。

    利用这条性质,我们把询问离线处理,从1 至 n 枚举答案的右端点 i。枚举到 i 时,我们去处理所有右端点 ≤ i 的询问:

    设当前询问区间为 [a,b],我们试着在 1~a 的位置 pos 中寻找一个满足 cnt=i-pos 条件的最大的 posmax。由于 i 是从小到大枚举的,posmax 是取最大的,这样得到的区间一定是最优解。

    维护 cnt 值和 pos 的最大值可以用线段树实现:当处理到第 i 个位置时,当前的值 xi 可能会和 xi-1 及 xi+1 使 cnt 值增加,记 xi±1 出现的位置为 posxi±1,所有 pos ≤ posxi±1 的点作为左端点,当前的 i 作为右端点的 cnt 值都要 +1,我们对区间 [1,posxi±1 ] 区间加;再维护最大的 cnt 和 cnt 最大时最大的 pos 即可。

    但是这样做复杂度最坏是 O(NM log N) 的。我们可能对一个询问一直找不到满足条件的 cnt=i-pos,会做很多次无用的询问。稍加思考我们发现线段树维护的 cnt 值随着右端点 i 的推进是不断增加的,那么在一大堆询问 [a,b] 中我们只要找最大的最大的 a 去查询 [1,amax] 的最值,就能知道之前有没有可能出现满足条件的 cnt 值。

    所有在处理询问时我们把询问塞到以左端点为关键字的大根堆里,每次取堆顶元素左端点查最值,如果不存在满足条件的值就去推进当前枚举的右端点 i,否则就去更新堆顶询问的答案,然后再从堆中取出询问来查询,直到堆为空或不存在满足条件的 cnt 值为止。这样 M 询问的查询次数均摊下来是 M 次,复杂度 O(N log N)。

    代码:(代码维护的是 pos+cnt 和 pos 值)

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<utility>
     5 #include<vector>
     6 #include<queue>
     7 using namespace std;
     8 template<class T>void read(T &x){
     9     x=0; char c=getchar();
    10     while(c<'0'||'9'<c)c=getchar();
    11     while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
    12 }
    13 typedef long long ll;
    14 typedef pair<ll,ll> lpair;
    15 template<class T>void cmax(T &x,T y){if(x<y)x=y;}
    16 const int N=100050;
    17 int n,m;
    18 int a[N],pos[N];
    19 vector<lpair>seq[N];
    20 priority_queue<lpair>ud;
    21 int ansl[N],ansr[N];
    22 struct segt{
    23 #define ls (p<<1)    
    24 #define rs (p<<1|1)    
    25 #define lson l,mid,p<<1
    26 #define rson mid+1,r,p<<1|1
    27     lpair s[N<<2]; ll tag[N<<2];
    28     void pushup(int p){s[p]=max(s[ls],s[rs]);}
    29     void pushdown(int p){
    30         if(!tag[p])return ;
    31         tag[ls]+=tag[p]; tag[rs]+=tag[p];
    32         s[ls].first+=tag[p]; s[rs].first+=tag[p];
    33         tag[p]=0;
    34     }
    35     void build(int l,int r,int p){
    36         if(l==r){s[p].first=s[p].second=l; return ;}
    37         int mid=(l+r)>>1;
    38         build(lson); build(rson);
    39         pushup(p);
    40     }
    41     void add(int L,int R,int l,int r,int p){
    42         if(L<=l&&r<=R){++tag[p]; ++s[p].first; return  ;}
    43         pushdown(p); int mid=(l+r)>>1;
    44         if(L<=mid)add(L,R,lson);
    45         if(R>mid)add(L,R,rson);
    46         pushup(p);
    47     }
    48     lpair query(int L,int R,int l,int r,int p){
    49         if(L<=l&&r<=R)return s[p];
    50         pushdown(p); int mid=(l+r)>>1; lpair ret=lpair(0,0);
    51         if(L<=mid)cmax(ret,query(L,R,lson));
    52         if(R>mid)cmax(ret,query(L,R,rson));
    53         return ret;
    54     }
    55 }t;
    56 int main(){
    57 //    freopen("sequence.in","r",stdin);
    58 //    freopen("sequence.out","w",stdout);
    59     read(n);
    60     for(int i=1;i<=n;i++){
    61         read(a[i]); pos[a[i]]=i;
    62     }
    63     t.build(1,n,1); read(m);
    64     for(int i=1,l,r;i<=m;i++){
    65         read(l); read(r);
    66         seq[r].push_back(lpair(l,i));
    67     }
    68     for(int i=1,tmp;i<=n;i++){
    69         for(int j=seq[i].size()-1;~j;j--)ud.push(seq[i][j]);
    70         if(tmp=pos[a[i]-1])if(tmp<i)t.add(1,tmp,1,n,1);
    71         if(tmp=pos[a[i]+1])if(tmp<i)t.add(1,tmp,1,n,1);
    72         while(!ud.empty()){
    73             lpair now=ud.top();
    74             lpair L=t.query(1,now.first,1,n,1);
    75             if(L.first==i)ansl[now.second]=L.second, ansr[now.second]=i, ud.pop();
    76             else break;
    77         }
    78     }
    79     for(int i=1;i<=m;i++)printf("%d %d\n",ansl[i],ansr[i]);
    80     return 0;
    81 }
    ~qwq~

     PS:之前有一个st表过去的是st表预处理答案而不是st表实现的暴力...

  • 相关阅读:
    LeetCode OJ--Sort Colors
    LeetCode OJ--Single Number II **
    LeetCode OJ--Single Number
    LeetCode OJ--Subsets II
    LeetCode OJ--ZigZag Conversion
    3ds Max学习日记(三)
    3ds Max学习日记(二)
    3ds Max学习日记(一)
    PokeCats开发者日志(十三)
    PokeCats开发者日志(十二)
  • 原文地址:https://www.cnblogs.com/opethrax/p/11303600.html
Copyright © 2011-2022 走看看