T1
有一个 n ∗ n n*n n∗n 的矩阵,每个格子有一个数,最开始在格子 ( 1 , 1 ) (1,1) (1,1),最终想要到达 ( n , n ) (n,n) (n,n),每一步只能往右或者往下走一格。
第 i i i 行,第 j j j 列的数是 A i ∗ B j m o d C A_i*B_j\mod C Ai∗BjmodC,一条路线的价值是路线上所有数的和,我们想要求出最大的价值以及这条路线,如果有价值相同的路线,输出任意一组解。
n ≤ 1 0 4 , 0 ≤ A i , B i , C ≤ 2 ∗ 1 0 4 n\leq 10^4,0\leq A_i,B_i,C\leq 2*10^4 n≤104,0≤Ai,Bi,C≤2∗104
2 s 2s 2s , 2 M B 2MB 2MB。
一开始想到直接用 b i t s e t bitset bitset 存储整个图的状态,然后直接倒退,发现只能通过 60 % 60\% 60% 的数据点,仔细思考一下发现可以用时间换空间,即倒着将行分成若干块,每一块的大小设为 B B B ,从后往前倒退,每次相当于只用处理行上一个前缀的答案,那么时间复杂度就变为 O ( n 3 B ) O(\frac {n^3} {B}) O(Bn3),带一个 1 2 \frac 1 2 21 的常数。
发现最大数据点还是超时,做了后面的题回来发现可以将行列同时分块,这样就省去了很多的时间复杂度,即将行列都按
B
B
B 分块,从最后一个块开始往前倒退,如果上一步在上面一块,那么下面的一整块在下一次Dp中都不需要管了,在左边的块同理。
最大的时候按照
B
=
3333
B=3333
B=3333 来划分可以做到大约
7
3
n
2
\frac 7 3n^2
37n2 ,十分优秀。
正解直接考虑分治,令
s
o
l
v
e
(
x
,
y
,
l
,
r
)
solve(x,y,l,r)
solve(x,y,l,r) 表示从
(
x
,
l
)
(x,l)
(x,l) 到
(
y
,
r
)
(y,r)
(y,r) 的答案。
用
O
(
(
y
−
x
)
(
r
−
l
)
)
O((y-x)(r-l))
O((y−x)(r−l)) 的时间可以处理出来经过第
m
i
d
=
(
x
+
y
)
/
2
mid=(x+y)/2
mid=(x+y)/2 行时最佳经过点
p
o
s
pos
pos 。
然后划分成
s
o
l
v
e
(
x
,
m
i
d
,
l
,
p
o
s
)
solve(x,mid,l,pos)
solve(x,mid,l,pos) 和
s
o
l
v
e
(
m
i
d
+
1
,
y
,
p
o
s
,
r
)
solve(mid+1,y,pos,r)
solve(mid+1,y,pos,r) 发现这样的面积恰好是原来的一半,所以时间复杂度就是
O
(
n
2
)
O(n^2)
O(n2) 空间复杂度是
O
(
n
)
O(n)
O(n) 。
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int N=10010,T=3334;
int n,C,a[N],b[N],p[N],f[N];
char s[N<<1];
bitset<T> w[T];
int main(){
scanf("%d %d",&n,&C);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
int t=0;
if(n<=T){
for(int i=1;i<=n;i++){
f[1]=f[1]+a[i]*b[1]%C;w[i-1][0]=0;
for(int j=2;j<=n;j++)
f[j]=(f[j]>f[j-1]?f[j]:(w[i-1][j-1]=1,f[j-1]))+a[i]*b[j]%C;
}
int x=n,y=n;
while(x!=1 || y!=1) s[++t]=(w[x-1][y-1]?(y--,'R'):(x--,'D'));
}
else{
int B=(n+T-1)/T;
for(int i=1;i<=B;i++) p[i]=n/B+(i<=n%B)+p[i-1];
int X=B,Y=B,x=n,y=n;
while(1){
for(int j=1;j<=y;j++) f[j]=0;
for(int i=1;i<=p[X-1];i++){
f[1]=f[1]+a[i]*b[1]%C;
for(int j=2;j<=y;j++)
f[j]=(f[j]>f[j-1]?f[j]:f[j-1])+a[i]*b[j]%C;
}
for(int i=p[X-1]+1;i<=x;i++) {
f[1]=f[1]+a[i]*b[1]%C;
if(p[Y-1]==0) w[i-p[X-1]-1][0]=0;
for(int j=2;j<=p[Y-1];j++) f[j]=(f[j]>f[j-1]?f[j]:f[j-1])+a[i]*b[j]%C;
for(int j=max(2,p[Y-1]+1);j<=y;j++)
f[j]=((f[j]>f[j-1])?(w[i-p[X-1]-1][j-p[Y-1]-1]=0,f[j]):(w[i-p[X-1]-1][j-p[Y-1]-1]=1,f[j-1]))+a[i]*b[j]%C;
}
while(x>p[X-1] && y>p[Y-1]){
if(x==1 && y==1) break;
if(w[x-p[X-1]-1][y-p[Y-1]-1]) y--,s[++t]='R';
else x--,s[++t]='D';
}
if(x==1 && y==1) break;
if(x<=p[X-1]) X--;
if(y<=p[Y-1]) Y--;
}
}
reverse(s+1,s+1+t);s[t+1]='\0';
printf("%s\n",s+1);
}
T2
给出一个 W ∗ H W*H W∗H 的网格,坐标从 ( 0 , 0 ) (0,0) (0,0) 到 ( W − 1 , H − 1 ) (W−1,H−1) (W−1,H−1)。有公共边的网格之间有 1 1 1 的边权。
有 n n n 个网格上有障碍,求所有非障碍点对间的最短路之和,保证非障碍点联通。
答案可能很大,对 1 0 9 + 7 10^9+7 109+7 取模。
对于 100 % 100\% 100% 的数据,保证 N ≤ 40 , W , H ≤ 1 0 6 , 0 ≤ x i ≤ W − 1 , 0 ≤ y i ≤ H − 1 , N\leq 40,W,H\leq 10^6,0\leq x_i\leq W−1,0\leq y_i\leq H−1, N≤40,W,H≤106,0≤xi≤W−1,0≤yi≤H−1, ,障碍点之间两两不同且非障碍点两两联通。
直接考虑划分成 ( 2 n + 1 ) ∗ ( 2 n + 1 ) (2n+1)*(2n+1) (2n+1)∗(2n+1) 个块,块内的最短路肯定没错,块间的最短路跑一下 b f s bfs bfs 。设块 a a a 到块 b b b 的最短路是 d d d ,但是曼哈顿距离为 p ≤ d p\leq d p≤d,那么块 a a a 的每一个点到块 b b b 的每一个点的最短路都比曼哈顿距离多了 d − p d-p d−p 。
证明还是挺简单的,因为障碍点都是 1 ∗ 1 1*1 1∗1 的。
#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
using namespace std;
const int N=100,mod=1000000007;
int n,w,h;
bool tf[N][N];
map<pair<int,int>,bool> p;
int a[N<<1],b[N<<1],xx[N],yy[N],ta,tb;
bool vis[N][N];
int fx[4]={-1,1,0,0};
int fy[4]={0,0,-1,1},ans,st,ed;
struct node{
int x,y,d;
}qs[N*N];
void bfs(int X,int Y){
qs[st=ed=1]=(node){X,Y,0};
for(int i=1;i<=ta;i++) for(int j=1;j<=tb;j++) vis[i][j]=false;
vis[X][Y]=true;
while(st<=ed){
int x=qs[st].x,y=qs[st].y,d=qs[st].d;st++;
for(int i=0;i<4;i++){
int xx=x+fx[i],yy=y+fy[i],dd=d+1;
if(xx>=1 && xx<=ta && yy>=1 && yy<=tb && !tf[xx][yy] && !vis[xx][yy]);
else continue;
vis[xx][yy]=true;
qs[++ed]=(node){xx,yy,dd};
if(X<xx || X==xx && Y<yy) ans=(ans+1ll*(dd-abs(X-xx)-abs(Y-yy))*(a[X]-a[X-1])%mod*(b[Y]-b[Y-1])%mod*(a[xx]-a[xx-1])%mod*(b[yy]-b[yy-1]))%mod;
}
}
}
int main(){
scanf("%d %d",&w,&h);
scanf("%d",&n);
for(int i=1;i<w;i++) ans=(ans+1ll*h*h%mod*(w-i)%mod*i)%mod;
for(int i=1;i<h;i++) ans=(ans+1ll*w*w%mod*(h-i)%mod*i)%mod;
for(int i=1;i<=n;i++) {
scanf("%d %d",&xx[i],&yy[i]);
xx[i]++;yy[i]++;
p[mp(xx[i],yy[i])]=true;
}
int tot=0;
for(int i=1;i<=n;i++) for(int j=1;j<i;j++) ans=(ans+abs(xx[i]-xx[j])+abs(yy[i]-yy[j]))%mod;
sort(xx+1,xx+1+n);
sort(yy+1,yy+1+n);
xx[n+1]=w;yy[n+1]=h;
for(int i=1;i<=n+1;i++) if(xx[i]!=xx[i-1]){
if(xx[i]>xx[i-1]+1) a[++ta]=xx[i]-1;
a[++ta]=xx[i];
}
for(int i=1;i<=n+1;i++) if(yy[i]!=yy[i-1]){
if(yy[i]>yy[i-1]+1) b[++tb]=yy[i]-1;
b[++tb]=yy[i];
}
for(int i=1;i<=ta;i++)
for(int j=1;j<=tb;j++) if(a[i]-a[i-1]==1 && b[j]-b[j-1]==1 && p[mp(a[i],b[j])]){
tf[i][j]=true;
ans=(ans+mod-(1ll*(a[i]-1)*a[i]/2+1ll*(w-a[i]+1)*(w-a[i])/2)%mod*h%mod+mod-(1ll*(b[j]-1)*b[j]/2+1ll*(h-b[j])*(h-b[j]+1)/2)%mod*w%mod)%mod;
}
for(int i=1;i<=ta;i++)
for(int j=1;j<=tb;j++) if(!tf[i][j])
bfs(i,j);
printf("%d\n",ans);
}
T3
有一个 n n n 个点的有向图,对于所有节点(除了 n n n),都恰好有两条出边,一条红色,一条蓝色。这些点在任意时刻都有恰好一条边是活动的,最开始蓝色边是活动的。开始节点 1 1 1 有一个棋子,每次棋子将当前点的活动边切换,并沿着活动边走向下一个节点,直到棋子走到 n n n 节点为止。本题保证所有点可以通过边走到 n n n(即将这个图视为一般有向图能到达)。
有 q q q 个查询,每次查询给出一个状态,状态形如一个二元组 ( v , s ) (v,s) (v,s),其中 v v v 是个一个数表示当前所在位置, s s s 是一个长度为 n − 1 n−1 n−1 的字符串表示当前活动边的颜色( B B B 表示蓝色, R R R 表示红色)。你需要回答这个状态第一次出现的时间,或者判定这个状态不可能出现,请注意,棋子切换活动边之后,再沿着边走(当然操作仍然是同时发生的)。
对于 100 % 100\% 100% 的数据, n ≤ 58 , q ≤ 5000 , 1 ≤ v ≤ n − 1 n\leq 58,q\leq 5000,1\leq v\leq n−1 n≤58,q≤5000,1≤v≤n−1.
神仙线性代数。
首先要学会构造未知数 x i , y i x_i,y_i xi,yi ,表示 i i i 点走了 x i x_i xi 次红边, y i y_i yi次蓝边。
根据状态 s s s 我们可以知道 y i = x i − [ s i = R ] y_i=x_i-[s_i=R] yi=xi−[si=R] ,因此可以用 x i x_i xi 表示 y i y_i yi。同时根据所在点 v v v 我们可以知道每个点出度和入度的关系,因此可以列出 n − 1 n-1 n−1 条方程,写成矩阵的形式可以得到 A x = w Ax=w Ax=w ,其中 A A A 矩阵在每一次询问中其实是不会改变的,那么可以得到 x = A − 1 w x=A^{-1}w x=A−1w,所以先将 A A A 的逆矩阵求出来,在询问中就能通过 w i w_i wi 快速计算 x i x_i xi 。
注意当计算矩阵的逆时,初等行(列)变换只有三种:
1.给一行(列)乘上某个非零数
k
k
k 。
2.给一行(列)减去另一行(列)的
k
≠
0
k\not =0
k=0 倍。
3.交换两行(列)。
可以证明这个
A
A
A 必定有逆。
然后将所有的
x
i
,
y
i
x_i,y_i
xi,yi 加起来就是答案了,由于答案可能很大,所以模两个大质数然后用中国剩余定理合并即可。
考虑如何判无解?首先 x i , y i ≥ 0 x_i,y_i\geq 0 xi,yi≥0,其次对于 x i ≠ 0 x_i\not = 0 xi=0的点(除了 v v v),最后一条活动边必定为以 v v v 为根的内向树,必要性显然。
充分性可以分成两种情况:
1.
v
v
v 的
x
i
,
y
i
x_i,y_i
xi,yi 均为
0
0
0,那么入活动边只有恰好一条(由出入度关系保证,没有入活动边当且仅当就是第一步,无需证明),直接回退即可。
2.
v
v
v 有上一条活动边,由于所有经过的点都指向了
v
v
v ,
v
v
v 的这条活动边指向的一定是一个曾经经过的点,否则出入度条件矛盾,那么回退到那个点所在的子树即可,状态唯一,其次满足归纳。