【模拟赛】纪中提高A组 19.8.5 测试

Posted on 2019-08-06 09:23  opethrax  阅读(153)  评论(2编辑  收藏  举报

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表实现的暴力...