[NOI2021 day1]轻重边(树链剖分),路径交点(矩阵行列式)

轻重边

description

solution

  • case=1~6

    把父亲和儿子的边转化为储存在儿子上的点

    建树,暴力爬 l c a lca lca,暴力修改, O ( n 2 ) O(n^2) O(n2)

  • case=A

    对于一条链的情况,每次修改重边就是一段区间,轻边只有两个端点相连的边

    同样的边化点,线性用线段树区间/单点修改,区间查询

  • case=B

    第二类操作询问只有一条边,考虑这条边怎么才可能是重边,显然就是两个点最后被经过的时间戳要一样

    树链剖分打时间戳

  • case=1~20

    正解其实就在case=B基础上,的确一条边如果是轻边当且仅当该边的两个端点所打时间戳不同

    树链剖分维护数颜色段的个数(轻边的数量),重边数量减一下就可以了

    最原始局面全都是轻边,所以初始化建树时就要给每个点打一个不同的时间戳

蒟蒻只想到所有到case=B的前 70 % 70\% 70%的部分分,实在没想到颜色段个数

code

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 100005
#define lson num << 1
#define rson num << 1 | 1
struct node {
	int cnt, l, r, tag;
}t[maxn << 2];
vector < int > G[maxn];
int T, n, m, cnt;
int dep[maxn], son[maxn], top[maxn], dfn[maxn], f[maxn], siz[maxn];

void dfs1( int u, int fa ) {
	dep[u] = dep[fa] + 1, f[u] = fa, siz[u] = 1;
	for( auto v : G[u] ) {
		if( v == fa ) continue;
		else dfs1( v, u );
		siz[u] += siz[v];
		if( siz[v] > siz[son[u]] )
			son[u] = v; 
	}
}

void dfs2( int u, int tt ) {
	top[u] = tt, dfn[u] = ++ cnt;
	if( son[u] ) dfs2( son[u], tt );
	else return;
	for( auto v : G[u] ) {
		if( v == f[u] || v == son[u] ) continue;
		else dfs2( v, v );
	}
}

void pushdown( int num ) {
	if( ! t[num].tag ) return;
	t[lson].l = t[lson].r = t[lson].tag = t[num].tag;
	t[rson].l = t[rson].r = t[rson].tag = t[num].tag;
	t[lson].cnt = t[rson].cnt = 1;
	t[num].tag = 0;
	return;
}

void build( int num, int l, int r ) {
	if( l == r ) {
		t[num].cnt = 1, t[num].l = t[num].r = l;
		return;
	}
	int mid = ( l + r ) >> 1;
	build( lson, l, mid );
	build( rson, mid + 1, r );
	t[num].l = t[lson].l, t[num].r = t[rson].r;
	t[num].cnt = t[lson].cnt + t[rson].cnt;
	t[num].tag = 0;
}

void modify( int num, int l, int r, int L, int R, int id ) {
	if( R < l || r < L ) return;
	if( L <= l && r <= R ) {
		t[num].cnt = 1, t[num].l = t[num].r = t[num].tag = id;
		return;
	}
	pushdown( num );
	int mid = ( l + r ) >> 1;
	modify( lson, l, mid, L, R, id );
	modify( rson, mid + 1, r, L, R, id );
	t[num].l = t[lson].l, t[num].r = t[rson].r;
	t[num].cnt = t[lson].cnt + t[rson].cnt - ( t[lson].r == t[rson].l );
}

void modify( int x, int y, int id ) {
	while( top[x] ^ top[y] ) {
		if( dep[top[x]] < dep[top[y]] ) swap( x, y );
		modify( 1, 1, n, dfn[top[x]], dfn[x], id );
		x = f[top[x]];
	}
	if( dep[x] > dep[y] ) swap( x, y );
	modify( 1, 1, n, dfn[x], dfn[y], id );
}

node query( int num, int l, int r, int L, int R ) {
	if( L <= l && r <= R ) return t[num];
	int mid = ( l + r ) >> 1;
	pushdown( num );
	if( R <= mid ) return query( lson, l, mid, L, R );
	else if( mid < L ) return query( rson, mid + 1, r, L, R );
	else {
		node ans1 = query( lson, l, mid, L, R );
		node ans2 = query( rson, mid + 1, r, L, R );
		node ans;
		ans.l = ans1.l, ans.r = ans2.r;
		ans.cnt = ans1.cnt + ans2.cnt - ( ans1.r == ans2.l );
		return ans;
	}
}

int query( int x, int y ) {
	node t;
	int ans = dep[x] + dep[y], tot = 0;
	int last_x = -1, last_y = -1;
	while( top[x] ^ top[y] ) {
		if( dep[top[x]] >= dep[top[y]] ) {
			t = query( 1, 1, n, dfn[top[x]], dfn[x] );
			tot += t.cnt;
			if( last_x == t.r ) tot --;
			last_x = t.l;
			x = f[top[x]];
		}
		else {
			t = query( 1, 1, n, dfn[top[y]], dfn[y] );
			tot += t.cnt;
			if( last_y == t.r ) tot --;
			last_y = t.l;
			y = f[top[y]];
		}
	}
	if( dep[x] > dep[y] ) swap( x, y ), swap( last_x, last_y );
	ans -= ( dep[x] << 1 );
	t = query( 1, 1, n, dfn[x], dfn[y] );
	tot += t.cnt;
	if( t.l == last_x ) tot --;
	if( t.r == last_y ) tot --;
	return ans - tot + 1;
}

int main() {
	scanf( "%d", &T );
	while( T -- ) {
		scanf( "%d %d", &n, &m );
		cnt = 0;
		for( int i = 1;i <= n;i ++ )
			G[i].clear(), son[i] = 0;
		for( int i = 1, u, v;i < n;i ++ ) {
			scanf( "%d %d", &u, &v );
			G[u].push_back( v );
			G[v].push_back( u );
		}
		dfs1( 1, 0 );
		dfs2( 1, 1 );
		build( 1, 1, n );
		for( int i = 1, opt, a, b;i <= m;i ++ ) {
			scanf( "%d %d %d", &opt, &a, &b );
			if( opt & 1 ) modify( a, b, i + n );
			else printf( "%d\n", query( a, b ) );
		}
	}
	return 0;
}

路径交点

description

solution

  • 图中的每个顶点至多出现在一条路径中是对本题正解算法的条件限制/保证

  • ( P j − Q j ) × ( P j + 1 − Q j + 1 ) < 0 (P_j-Q_j)\times(P_{j+1}-Q_{j+1})<0 (PjQj)×(Pj+1Qj+1)<0

    当固定第 j j j层的枚举顺序时,发现这个条件限制的本质是逆序对

  • 询问有偶数个交点的路径方案数比有奇数个交点的路径方案数多多少个

    一种方案的贡献显然是 ( − 1 ) c n t , c n t (-1)^{cnt},cnt (1)cnt,cnt表示这种方案下的逆序对个数

  • 相邻层给出的若干条有向边,可以构建一个邻接矩阵

  • 综上所有的信息都在暗示/匹配矩阵的行列式

    ∣ P ∣ = ∑ j 1 , j 2 , . . . , j n ( − 1 ) τ ( j 1 , j 2 . . . j n ) a 1 , j 1 . . . a n , j n |P|=\sum_{j_1,j_2,...,j_n}(-1)^{\tau(j_1,j_2...j_n)}a_{1,j_1}...a_{n,j_n} P=j1,j2,...,jn(1)τ(j1,j2...jn)a1,j1...an,jn

    ∑ j 1 , j 2 , . . . , j n : j \sum_{j_1,j_2,...,j_n}:j j1,j2,...,jn:j n n n级全排列求和, τ ( j 1 , j 2 , . . . , j n ) \tau(j_1,j_2,...,j_n) τ(j1,j2,...,jn)表示排列 j 1 , . . . , j n j_1,...,j_n j1,...,jn的逆序对个数

  • 知道二分图的解决方法了,接下来要扩展成 k k k层图的计算

    binet-cauchy公式 ∣ A B ∣ = ∣ A ∣ ∣ B ∣ |AB|=|A||B| AB=AB

    所以只需要算出相邻两层的矩阵,然后矩阵乘法得到最后为 n 1 × n 1 n_1\times n_1 n1×n1的总矩阵

    最后通过高斯消元(上三角矩阵)计算主对角线的乘积就是矩阵的行列式,也是询问的答案

code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 998244353
#define int long long
#define maxn 205
struct matrix {
	int n, m;
	int c[maxn][maxn];
	matrix() {
		n = m = 0;
		memset( c, 0, sizeof( c ) );
	}
	void clear() {
		for( int i = 1;i <= n;i ++ )
			for( int j = 1;j <= m;j ++ )
				c[i][j] = 0;
	}
	matrix operator * ( matrix &t ) const {
		matrix ans;
		ans.n = n, ans.m = t.m;
		for( int i = 1;i <= n;i ++ )
			for( int k = 1;k <= m;k ++ )
				if( c[i][k] )
					for( int j = 1;j <= t.m;j ++ )
						ans.c[i][j] = ( ans.c[i][j] + c[i][k] * t.c[k][j] ) % mod;
		return ans;
	}
}last, now;

int qkpow( int x, int y ) {
	int ans = 1;
	while( y ) {
		if( y & 1 ) ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}

void gauss( int a[][maxn], int n ) {
	int ans = 1;
	for( int i = 1;i <= n;i ++ ) {
		int k = i;
		for( int j = i + 1;j <= n;j ++ )
			if( a[j][i] > a[k][i] ) k = j;
		if( i ^ k ) swap( a[i], a[k] ), ans *= -1;
		for( int j = i + 1;j <= n;j ++ ) {
			int t = a[j][i] * qkpow( a[i][i], mod - 2 ) % mod;
			for( int k = i;k <= n;k ++ )
				a[j][k] = ( a[j][k] - t * a[i][k] % mod + mod ) % mod;
		}
	}
	for( int i = 1;i <= n;i ++ )
		ans = ans * a[i][i] % mod;
	printf( "%lld\n", ( ans + mod ) % mod );
}

int T, n;
int vec[maxn], edge[maxn];
signed main() {
	scanf( "%lld", &T );
	while( T -- ) {
		scanf( "%lld", &n );
		for( int i = 1;i <= n;i ++ )
			scanf( "%lld", &vec[i] );
		for( int i = 1;i < n;i ++ )
			scanf( "%lld", &edge[i] );
		last.n = vec[1], last.m = vec[2], last.clear();
		for( int i = 1, u, v;i <= edge[1];i ++ ) {
			scanf( "%lld %lld", &u, &v );
			last.c[u][v] = 1;
		}
		for( int i = 2;i < n;i ++ ) {
			now.n = vec[i], now.m = vec[i + 1], now.clear();
			for( int j = 1, u, v;j <= edge[i];j ++ ) {
				scanf( "%lld %lld", &u, &v );
				now.c[u][v] = 1;
			}
			last = last * now;
		}
		gauss( last.c, vec[1] );
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值