\(\text{CF480E Parking Lot}\)
解法
\(\text{Method 1}\):\(\mathcal O(nmk)\)
这个都没想到真的不应该...
对于每次修改都暴力改,然后进行一个 \(\mathcal O(nm)\) 的 \(\mathtt{dp}\):令 \(dp_{i,j}\) 为以 \((i,j)\) 为右下角的最大正方形。就有:
具体画三个 \(\mathtt{dp}\) 值对应的正方形就可以感性地证明。
\(\text{Method 2}\):\(\mathcal O(n^2)\)
当修改具有永久性,我们可以尝试将其离线,观察某种顺序遍历是否具有性质。
比如这题如果倒序遍历修改,就有个非常神奇的性质 —— 如果更新了最大正方形,那么这个正方形一定包含被撤销的障碍!
据此可以维护 \(up_{i,j},down_{i,j}\) 分别表示从 \((i,j)\) 开始向上/下能延伸的最长长度。每次撤销障碍修改是 \(\mathcal O(n)\) 的。
如何求解此时的最大正方形?直接求解似乎不太好搞,我们可以看一下如何 \(\rm check\) 长度为 \(l\) 的正方形。由于这个正方形包含了被撤销的障碍 \((x,y)\),不妨以第 \(x\) 行为基准线,分别往上、往下延伸,再把它们拼在一起。
比如对于往上延伸,从第一列往右扫,对于 \(up_{x,j}\) 维护一个单增的单调队列。因为只 \(\rm check\) 长度为 \(l\) 的正方形,所以当队列长度大于 \(l\) 时可以踢掉队头,使长度更大。单次是 \(\mathcal O(m)\) 的。
但是这只是 \(\rm check\) 啊?其实可以每次暴力扩展 \(ans+1\)。首先,可以保证 \(\rm check\) 只能 成功 \(\min\{n,m\}\) 次。其次,对于 \(k\) 次询问,每次只可能有一次 失败,所以总时间复杂度 \(\mathcal O(nk+m(n+k))\)。
\(\text{Method 3}\):\(\mathcal O(n^2)\)
如果想到了倒序修改,但是没有想到暴力扩展 \(ans+1\)?可以用尺取法来 \(\mathcal O(m)\) 地计算最大正方形。
具体而言,设置两个指针 \(l,r\),假设当前上下能扩展的最长长度为 \(L\),那么如果 \(r-l+1\ge L\),此时再扩展 \(r\) 指针显然没有任何意义。于是将 \(l\) 指针加一。反之,则继续扩展。
\(\text{Method 4}\):\(\mathcal O(n^2)\)
这题有在线做法,但是我太弱没看懂,所以就咕咕咕了。
代码
在具体实现时,用的是开区间。
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <iostream>
using namespace std;
const int maxn=2005;
char g[maxn][maxn];
int n,m,k,Qx[maxn],Qy[maxn],A[maxn];
int up[maxn][maxn],down[maxn][maxn];
class Queue {
public:
int l,r,q[maxn],v[maxn],tp;
void Clear() {l=1,r=tp=0;}
void Push(int k) {
v[++tp]=k;
while(l<=r and v[q[r]]>=k) --r;
q[++r]=tp;
}
void Pop(int x) {
while(l<=r and q[l]<=x) ++l;
}
int Top() {
return l<=r?v[q[l]]:0;
}
} Up,Down;
void U_update(int j) {
for(int i=1;i<=n;++i)
if(g[i][j]^'X')
up[i][j]=up[i-1][j]+1;
else up[i][j]=0;
}
void D_update(int j) {
for(int i=n;i>=1;--i)
if(g[i][j]^'X')
down[i][j]=down[i+1][j]+1;
else down[i][j]=0;
}
int FuckIt(int i) {
if(!i) return 0;
int ans=0,L=0;
Up.Clear(),Down.Clear();
for(int l=1,r=1;l<=m;++l) {
Up.Pop(l-1),Down.Pop(l-1);
do {
if(r<=m) {
Up.Push(up[i][r]);
Down.Push(down[i][r]);
++r;
}
L=Up.Top()+Down.Top()-1;
ans=max(ans,min(L,r-l));
} while(r<=m and r-l<L);
}
return ans;
}
int main() {
n=read(9),m=read(9),k=read(9);
for(int i=1;i<=n;++i)
scanf("%s",g[i]+1);
for(int i=1;i<=k;++i)
Qx[i]=read(9),Qy[i]=read(9),
g[Qx[i]][Qy[i]]='X';
for(int i=1;i<=m;++i)
U_update(i),D_update(i);
for(int i=1;i<=n;++i)
A[k+1]=max(A[k+1],FuckIt(i));
for(int i=k;i>=1;--i) {
A[i]=max(A[i+1],FuckIt(Qx[i+1]));
g[Qx[i]][Qy[i]]='.';
U_update(Qy[i]);
D_update(Qy[i]);
}
for(int i=1;i<=k;++i)
print(A[i],'\n');
return 0;
}
\(\text{CF575A Fibonotci}\)
解法
当时一场考试好像三个半小时,我调这道题调了整场考试… 最后发现思路都挂了。
先令
那么就有
用线段树维护 \(a_1\) 到 \(a_n\) 矩阵的乘积,将每个修改拆成两个,这样对于在 \([kn+1,kn+n]\) 中的修改在线段树上修改即可。如果在 \([ln+1,rn]\) 之间都没有修改的话,直接快速幂就行了。
\(n,m\) 同阶,总时间复杂度 \(\mathcal O(8n\log n)\)。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; bool f=0; char s;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=5e4+5;
int n,m,mod,val[maxn];
ll K;
struct Modi {
ll x; int y;
bool op;
} q[maxn<<1];
inline int inc(int x,int y) {
return x+y>=mod?x+y-mod:x+y;
}
struct mat {
int a[2][2];
mat() {memset(a,0,sizeof a);}
mat operator * (const mat &t) const {
mat r;
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
if(a[i][j])
for(int k=0;k<2;++k)
r.a[i][k]=inc(
r.a[i][k],
1ll*a[i][j]*t.a[j][k]%mod);
return r;
}
} t[maxn<<2],cur,e,what,a[maxn],b[maxn];
mat qkpow(ll y) {
mat r;
for(int i=0;i<2;++i)
r.a[i][i]=1;
while(y) {
if(y&1) r=r*e;
e=e*e; y>>=1;
}
return r;
}
void build(int o,int l,int r) {
if(l==r)
return t[o]=a[l],void();
int mid=l+r>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
t[o]=t[o<<1]*t[o<<1|1];
}
void modify(int o,int l,int r,int p) {
if(l==r)
return t[o]=what,void();
int mid=l+r>>1;
if(p<=mid)
modify(o<<1,l,mid,p);
else modify(o<<1|1,mid+1,r,p);
t[o]=t[o<<1]*t[o<<1|1];
}
inline int ID(ll x) {
return x%n==0?n:x%n;
}
int main() {
K=read(9ll),mod=read(9);
n=read(9);
for(int i=0;i<n;++i)
val[i]=read(9)%mod;
for(int i=1;i<=n;++i) {
a[i].a[1][0]=1;
a[i].a[0][1]=val[i-1];
a[i].a[1][1]=val[i%n];
b[i]=a[i];
}
m=read(9);
for(int i=1;i<=m;++i)
q[i].x=read(9ll),
q[i+m].x=q[i].x+1,
q[i].op=1,
q[i+m].y=q[i].y=read(9)%mod;
m<<=1;
sort(q+1,q+m+1,[](Modi a,Modi b) {
return a.x<b.x;
});
build(1,1,n);
cur.a[0][1]=1;
ll pos=0; int j;
for(int i=1;i<=m;i=j+1) {
if(q[i].x>K) break;
if(pos+n<q[i].x) {
e=t[1];
cur=cur*qkpow((q[i].x-1-pos)/n);
}
pos=(q[i].x-1)/n*n+n;
j=i;
while(j+1<=m and (q[i].x-1)/n==(q[j+1].x-1)/n)
++j;
for(int k=i;k<=j;++k) {
b[ID(q[k].x)].a[q[k].op][1]=q[k].y;
what=b[ID(q[k].x)];
modify(1,1,n,ID(q[k].x));
}
if((q[i].x-1)/n==(K-1)/n) {
pos-=n;
break;
}
cur=cur*t[1];
for(int k=i;k<=j;++k)
what=b[ID(q[k].x)]=a[ID(q[k].x)],
modify(1,1,n,ID(q[k].x));
}
if(pos+n<K) {
e=t[1];
cur=cur*qkpow((K-1-pos)/n);
pos=(K-1)/n*n;
}
for(ll i=pos+1;i<=K;++i)
cur=cur*b[ID(i)];
print(cur.a[0][0],'\n');
return 0;
}