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 }
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 }
我们可以继续思考优美区间还具有哪些性质。如果记 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 }
上面两个性质我们想不到什么更好的做法了,我们再去找找其他的性质。
根据 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 }
PS:之前有一个st表过去的是st表预处理答案而不是st表实现的暴力...