ややこしいけど何とか。
https://yukicoder.me/problems/no/655
問題
整数Nが与えられたとき、以下の正三角形状に頂点が並んだ無向グラフを考える。
頂点がN段あり、上からi段目にはi個の頂点が並んでいる。上からx段、左からy列目の頂点を(x,y)と表す。
各頂点は、同じ段の左右隣り、および上下段の左右近傍頂点2個ずつ計最大6個の点に隣接しており、その間で辺が張られている。
ここでいくつかの頂点が指定される。
この状態で、各頂点(x,y)のスコアA(x,y)は、最寄りの指定頂点までの最短距離で定義される。
ここで、正三角形状の区間内の頂点群を選んだ時、スコアの総和がP以上となるのは何通りか。
解法
A(x,y)はまずダイクストラの要領で求めればO(N^2)で解ける。
問題は数え上げパートで、頂点群の選び方はO(N^3)通りあるので1つずつ数えていくと間に合わない。
そこで累積和と尺取り法を用いる。
下辺の両端を決めれば正三角形の形状が定まり、内部のスコアの総和も求まる。
よって下辺の両端を尺取り法しよう。
下辺の段は総当たりする。
今x段目を見ていくとき、下辺の左端をL、右端をRとする。
するとこの2点を可変の両端とする正三角形は(x,L)(x,R)(x-(R-L),L)で囲まれた領域となる。
Lを1ずつずらしたとき、尺取り法の要領で正三角形内のスコアの総和がPを超えない範囲でRを大きくしよう。この手順は均しO(N^2)で解ける。
そうすると、逆に正三角形の左下を(x,L)としたとき、右下を(x,R+1)~(x,x)にしたときP以上になる。
問題はL,Rをずらしたときの正三角形内のスコアの変化量である。
(x,L)(x,R)(x-(R-L),L)で囲まれた領域において、Lをインクリメントすると(x,L)-(x-(R-L),L)の頂点が正三角形から外になる。
またRをインクリメントすると(x,R+1)-(x-(R+1-L),L)の頂点が正三角形に入る。
よって左下-右上方向および右下-左上方向の2通り1次元累積和を事前に取っておけば、スコアの変化量がO(1)で評価できる。
2次元累積和で頑張ってもO(1)で求められるね。
int N,K; ll P; int D[4040][4040]; int L[4040][4040]; int R[4040][4040]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N>>K>>P; FOR(x,N) FOR(y,N) D[x][y]=101010; queue<pair<int,int>> Q; FOR(i,K) { cin>>x>>y; x--,y--; D[x][y]=0; Q.push({x,y}); } while(Q.size()) { x=Q.front().first; y=Q.front().second; Q.pop(); int dx[6]={-1,0,-1,1,0,1}; int dy[6]={-1,-1,0,0,1,1}; FOR(i,6) { int tx=x+dx[i]; int ty=y+dy[i]; if(tx<0 || tx>=N || ty<0 || ty>tx) continue; if(D[tx][ty]>D[x][y]+1) { D[tx][ty]=D[x][y]+1; Q.push({tx,ty}); } } } for(x=N-1;x>=0;x--) { FOR(y,x+1) { L[x][y]=D[x][y]+L[x+1][y]; R[x][y]=D[x][y]+R[x+1][y+1]; } } ll ret=0; for(x=N-1;x>=0;x--) { ll S=0; for(int LL=0,RR=-1;LL<=x;LL++) { if(RR<=LL) RR=LL, S=0; while(RR<=x&&S+(R[x-(RR-LL)][LL]-R[x+1][RR+1])<P) { S+=(R[x-(RR-LL)][LL]-R[x+1][RR+1]); RR++; } ret+=x+1-RR; S-=(L[x-(RR-1-LL)][LL]-L[x+1][LL]); } } cout<<ret<<endl; }
まとめ
2次元以上の累積和は頭が混乱するので、無理やり1次元の累積和を駆使するの好き。