【8.29校内测试】【分层图最短路】【数学公式推导?】
考场上一眼就觉得是$Astar$!赶快拍完又调了半天结果大样例卡成粑粑...所以索很玄学要少用啊...
考后看到$fyt$的代码简直就crazy叻!!不就是个分层图最短路DP吗!!所以水题刷的不够多啊...
定义状态$dp[u][k]$表示当前到$u$点,途径了$k$个点能走的最短距离,因为距离要小于$L$当然是越短越好啊!就可以刷表转移了...
#include<iostream> #include<cstdio> #include<cstring> #include<map> #include<queue> #define LL long long using namespace std; LL dp[5005][5005]; int V, M, N, E, S, T; LL L; struct Node { int u, cnt; Node ( int u = 0, int cnt = 0 ) : u ( u ), cnt ( cnt ) { } }; struct Edge { int v, nex; LL w; Edge ( int v = 0, LL w = 0, int nex = 0 ) : v ( v ), w ( w ), nex ( nex ) { } } edge[10005]; int stot, h[5005]; void add ( int u, int v, LL s ) { edge[++stot] = Edge ( v, s, h[u] ); h[u] = stot; } int vis[5005][5005]; void Spfa ( ) { queue < Node > q; q.push ( Node ( S, 0 ) ); memset ( dp, 0x3f3f3f3f, sizeof ( dp ) ); vis[S][0] = 1; dp[S][0] = 0; while ( !q.empty ( ) ) { Node x = q.front ( ); q.pop ( ); int u = x.u, cnt = x.cnt; vis[u][cnt] = 0; for ( int i = h[u]; i; i = edge[i].nex ) { int v = edge[i].v; if ( dp[v][cnt+1] > dp[u][cnt] + edge[i].w && dp[u][cnt] + edge[i].w <= L ) { dp[v][cnt+1] = dp[u][cnt] + edge[i].w; if ( !vis[v][cnt+1] ) { vis[v][cnt+1] = 1; q.push ( Node ( v, cnt + 1 ) ); } } } } } int main ( ) { freopen ( "park.in", "r", stdin ); freopen ( "park.out", "w", stdout ); scanf ( "%d%d%d%d%I64d", &V, &M, &N, &E, &L ); S = 0, T = V + 1; for ( int i = 1; i <= M; i ++ ) { int s; LL a; scanf ( "%d%I64d", &s, &a ); add ( S, s, a ); } for ( int i = 1; i <= N; i ++ ) { int t; LL b; scanf ( "%d%I64d", &t, &b ); add ( t, T, b ); } for ( int i = 1; i <= E; i ++ ) { int u, v; LL s; scanf ( "%d%d%I64d", &u, &v, &s ); add ( u, v, s ); } Spfa ( ); for ( int i = V + 1; i >= 0; i -- ) if ( dp[T][i] <= L ) { if ( i ) printf ( "%d", i - 1 ); else printf ( "0" ); break; } return 0; }
数据范围和题意好分块啊...QAQ,大样例欺骗我!!
正解是推公式,通过部分预处理和部分$O(1)$计算得到,我们发现,所有点满足条件的位置$des[i]$是单调不降的,而且第一个满足了后面一定都满足。所以首先想到每次询问,二分出最后一个$des[i]$在$R$以内的$pos$,$L$到$pos$的贡献都要计算在内。
然后发现,我们要求的实际上是$\sum_{i=L}^{pos}{\frac{(des[i]-i+R-i)(R-des[i]+1)}{2}}$,后面的一坨是已经化简过的所有$j-i$的等差数列。
拆开得到一堆东西,只和$des[i]$和$i$有关的可以$O(n)$预处理,只和$R$有关的可以直接$*(pos-L+1)$,和$i$和$R$有关的用等差数列$O(1)$算,直接得出答案。
预处理$des[i]$的时候用划窗。
注意long long!
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; LL n, m, q, a[100005]; LL color[100005], Q[100005], des[100005]; LL sum[100005]; LL erfen ( LL L, LL R ) { LL l = L, r = R, ans = L - 1; while ( l <= r ) { LL mid = ( l + r ) >> 1; if ( des[mid] <= R && des[mid] ) ans = mid, l = mid + 1; else r = mid - 1; } return ans; } int main ( ) { freopen ( "plan.in", "r", stdin ); freopen ( "plan.out", "w", stdout ); scanf ( "%I64d%I64d%I64d", &n, &m, &q ); for ( LL i = 1; i <= n; i ++ ) scanf ( "%I64d", &a[i] ); LL h = 1, t = 0, cnt = 0; for ( LL i = 1; i <= n; i ++ ) { Q[++t] = i; color[a[i]] ++; if ( color[a[i]] == 1 ) cnt ++; while ( cnt == m ) { des[Q[h]] = t; color[a[Q[h]]] --; if ( color[a[Q[h++]]] == 0 ) cnt --; } } for ( LL i = 1; i <= n; i ++ ) { LL tmp = -1LL * des[i] * des[i] + des[i] + 1LL * 2 * i * des[i] - 1LL * 2 * i; sum[i] = sum[i-1] + tmp; } for ( LL i = 1; i <= q; i ++ ) { LL L, R; scanf ( "%I64d%I64d", &L, &R ); LL pos = erfen ( L, R ); if ( pos < L ) printf ( "0\n" ); else { LL ans; LL tmp1 = 1LL * ( R * R + R ) * ( pos - L + 1 ); LL tmp2 = -1LL * ( L + pos ) * ( pos - L + 1 ) * R; LL tmp3 = 1LL * ( sum[pos] - sum[L-1] ); ans = 1LL * ( tmp1 + tmp2 + tmp3 ) / 2; printf ( "%I64d\n", ans ); } } return 0; }