ZROI#958
ZROI#958
ZROI#958
这题难吗?难!
考场上得分了吗?没.
为啥不得分?菜!
为啥这么菜?不知道...(知道就不这么菜了)
这题显然可以二分答案.
二分最多连续走过多少相同的边.
然后你可以选择拆点转移,也可以选择直接\(dis_{0/1,i}\)作为状态用最短路转移.
如果你拆点的话,假定你二分的答案是\(mid\),那么你就把每个点拆成\(mid\times 2\)个点.
前\(mid\)个点表示连续走\(1\)到\(mid\)步白边能走到这个点,后\(mid\)个点同理.
然后这样很显然地,如果最后图连通这个答案就可行,否则不可行.
然后我们发现只记录到每个点的路径中最后一段连续地黑边/白边有多少就可以转移.
转移好像不太难?然后我们就只需要判断\(1\)号点到\(n\)号点是否连通就行了.
这样是\(\Theta(n^2log_2{n})\)的.只能通过前两个子任务.
\(Code:\)
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = a ; i <= b ; ++ i)
#define per(i,a,b) for (int i = a ; i >= b ; -- i)
#define pii pair < int , int >
#define X first
#define Y second
#define rint read<int>
#define int long long
#define pb push_back
#define mid ( ( l + r ) >> 1 )
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
using std::queue ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 2e5 + 100 ;
const int inf = 0x7f7f7f7f ;
struct edge { int to , next ; bool type ; } e[N<<1] ;
int tot , head[N] , n , dis[N][2] ;
queue < int > q ; bool mk[N] ;
inline void build (int u , int v , bool k) {
e[++tot].next = head[u] ; e[tot].to = v ;
e[tot].type = k ; head[u] = tot ; return;
}
inline bool SPFA (int x) {
rep ( i , 1 , n ) dis[i][0] = dis[i][1] = inf ;
q.push ( 1 ) ; MEM ( mk , 0 ) ; mk[1] = true ;
dis[1][0] = dis[1][1] = 0 ;
while ( ! q.empty () ) {
int j = q.front () ; q.pop () ; mk[j] = false ;
for (int i = head[j] ; i ; i = e[i].next) {
int k = e[i].to ; bool d = e[i].type ;
if ( dis[j][d^1] <= x && dis[k][d] >= 2 ) { // 指定更新另一种边,尝试最小化答案
dis[k][d] = 1 ; if ( ! mk[k] ) mk[k] = true , q.push ( k ) ;
}
if ( dis[j][d] >= x ) continue ;
if ( dis[k][d] > dis[j][d] + 1 ) { // 更新自身这种边
dis[k][d] = dis[j][d] + 1 ;
if ( ! mk[k] ) mk[k] = true , q.push ( k ) ;
}
}
}
return dis[n][1] < inf || dis[n][0] < inf ;
}
signed main (int argc , char * argv[]) {
n = rint () ;
rep ( i , 1 , n ) {
int u = rint () , v = rint () ;
build ( i , u , 0 ) ; build ( i , v , 1 ) ;
}
int l = 1 , r = n , ans = - 1 ;
while ( l <= r ) {
if ( SPFA ( mid ) ) ans = mid , r = mid - 1 ;
else l = mid + 1 ;
}
printf ("%lld\n" , ans ) ;
system ("pause") ; return 0 ;
}
至于第三个子任务,是给拆点+倍增优化建图的,不会,所以不管.正解做法也可能比较自然吧,反正我是和刁神讨论(其实是被教导)很久才算是明白.
我们考虑对于每种颜色的边每个点只会有一个唯一的后继,那么连续走相同颜色的边走\(k\)步的后继也是唯一的.
我们考虑用并查集去维护从某个点往下沿着某种颜色的边最远能走到哪个点,同时维护一个权值:两点之间的距离(即需要多少此种类型的边)
我们发现,一个点的后继是构成了一条链(刨去环,每个连通块都是一条链),我们这里的并查集就相当于是之前在序列上维护一个元素向右的第一个与自身不同的元素一样.
但因为我们不是序列,所以距离要额外维护,这就导致了并查集变成了加权并查集.权值在路径压缩的时候维护.
我们每次就从并查集里找最远的点去更新,这样就不会有冗余的点入队,而且每个点最多被更新两次,黑白各一次,同时考虑通过强制更新另一种边来尝试延伸扩展.
延伸扩展是指:你考虑,你用一种边走了\(x\)步,你只剩下了\(mid-x\)步,而如果这时你用另一种边强制更新,那么你剩余步数就变成了\(mid-1\)步.有可能能使步数扩展,而使得图连通.
这样总复杂度\(\Theta(n\:log_2{n}\:\alpha(n))\).
可以通过最后一子任务.
\(Code:\)
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
#include <ctime>
#include <map>
#include <set>
#define MEM(x,y) memset ( x , y , sizeof ( x ) )
#define rep(i,a,b) for (int i = a ; i <= b ; ++ i)
#define per(i,a,b) for (int i = a ; i >= b ; -- i)
#define pii pair < int , int >
#define X first
#define Y second
#define rint read<int>
#define pb push_back
#define mid ( ( l + r ) >> 1 )
using std::set ;
using std::pair ;
using std::max ;
using std::min ;
using std::priority_queue ;
using std::vector ;
using std::swap ;
using std::sort ;
using std::unique ;
using std::greater ;
using std::queue ;
template < class T >
inline T read () {
T x = 0 , f = 1 ; char ch = getchar () ;
while ( ch < '0' || ch > '9' ) {
if ( ch == '-' ) f = - 1 ;
ch = getchar () ;
}
while ( ch >= '0' && ch <= '9' ) {
x = ( x << 3 ) + ( x << 1 ) + ( ch - 48 ) ;
ch = getchar () ;
}
return f * x ;
}
const int N = 2e5 + 100 ;
struct edge { int to , next ; bool data ; } e[N<<1] ;
int n , tot , nxt[2][N] ;
bool vis[2][N] ;
int f[2][N] , dis[2][N] ;
inline int getf(int x , bool k) {
if ( f[k][x] == x ) return x ;
int anc = f[k][x] ;
f[k][x] = getf ( f[k][x] , k ) ;
dis[k][x] += dis[k][anc] ;
return f[k][x] ;
}
queue < pii > q ;
inline bool check (int len) {
MEM ( vis , 0 ) ; MEM ( dis , 0 ) ;
rep ( i , 1 , n ) f[0][i] = f[1][i] = i ;
while ( ! q.empty () ) q.pop () ;
vis[1][1] = vis[0][1] = true ;
q.push ( { 1 , 1 } ) ; q.push ( { 0 , 1 } ) ;
while ( ! q.empty () ) {
pii j = q.front () ; q.pop () ;
if ( j.Y == n ) return true ;
int x = j.X , y = j.Y , res = 0 ;
while ( true ) {
int fx = getf ( y , x ) ; res = dis[x][y] ; ++ res ;
int to = nxt[x][fx] ; int fy = getf ( to , x ) ;
if ( res > len ) break ;
if ( ! vis[x^1][to] ) vis[x^1][to] = true , q.push ( { x ^ 1 , to } ) ;
if ( fx == fy ) break ; f[x][fx] = to ; dis[x][fx] = 1 ;
}
}
return vis[0][n] || vis[1][n] ;
}
signed main (int argc , char * argv[]) {
n = rint () ;
rep ( i , 1 , n ) nxt[0][i] = rint () , nxt[1][i] = rint () ;
int l = 1 , r = n , ans = - 1 ;
while ( l <= r ) {
if ( check ( mid ) ) ans = mid , r = mid - 1 ;
else l = mid + 1 ;
}
printf ("%d\n" , ans ) ;
system ("pause") ; return 0 ;
}