【无码专区9】序列统计(带权并查集 + 前缀和建边 + dp)

因为只有std,没有自我实现,所以是无码专区

主要是为了训练思维能力

solution才是dls正解,但是因为只有潦草几句,所以大部分会有我自己基于正解上面的算法实现过程,可能选择的算法跟std中dls的实现不太一样。

std可能也会带有博主自己的注释。


problem

n n n 个整数,第 i i i 个整数在 [ x i , y i ] [x_i,y_i] [xi,yi] 区间。

给定 m m m 个限制,形如 l i , r i , s i l_i,r_i,s_i li,ri,si ,要求第 l i l_i li r i r_i ri 的数字加起来对 2 2 2 取模余数为 s i s_i si

求有多少种整数序列满足上面限制,答案对 1 0 9 + 7 10^9+7 109+7 取模。

以及输出字典序最小的整数序列。

n ≤ 40 , m ≤ 100 , 0 ≤ x i ≤ y i ≤ 1 e 9 n\le 40,m\le 100,0\le x_i\le y_i\le 1e9 n40,m100,0xiyi1e9

1 s , 128 M B 1s,128MB 1s,128MB


my idea

因为限制是在模 2 2 2 意义下的,所以真的有关系的只是这个数是奇数还是偶数。

对于 50 % 50\% 50% 的数据点 n ≤ 20 n\le 20 n20,直接 2 n 2^n 2n 枚举每一位是选的奇数还是偶数,然后枚举所有限制判断是否合法,顺道可以记录一下字典序最小解及个数,时间复杂度 O ( 2 n m ) O(2^nm) O(2nm)

[ l i , r i ] [l_i,r_i] [li,ri] 的限制转化为前缀和做差的限制,即 s u m ( r ) − s u m ( l − 1 ) sum(r)-sum(l-1) sum(r)sum(l1)

s u m ( i ) sum(i) sum(i) 看作一个点,将一个限制看作 l i − 1 → r i l_i-1\rightarrow r_i li1ri 的有向边,边权为 s i s_i si

这样会形成若干个相互独立的有向图。

考虑将这些图,以及图内一个点可能引发的若干条边合并起来,变成一条链。

i.e. 要求 [ 3 , 5 ] = 1 , [ 4 , 8 ] = 0 , [ 4 , 7 ] = 1 ⇒ 2 → 5 ( 1 ) , 3 → 8 ( 0 ) , 3 → 7 ( 1 ) [3,5]=1,[4,8]=0,[4,7]=1\Rightarrow 2\rightarrow 5(1),3\rightarrow 8(0),3\rightarrow 7(1) [3,5]=1,[4,8]=0,[4,7]=125(1),38(0),37(1)

⇒ 2 → 3 ( x ) → 5 ( x ⨁ 1 ) → 7 ( x ⨁ 1 ) → 8 ( x ) \Rightarrow 2\rightarrow 3(x)\rightarrow 5(x\bigoplus 1)\rightarrow 7(x\bigoplus1)\rightarrow 8(x) 23(x)5(x1)7(x1)8(x)

当最开始的一条边上的权确定时,整条链就确定了。

具体而言:直接枚举两两限制进行合并, [ l 1 , r 1 ] [ l 2 , r 2 ] , l 1 < l 2 < r 1 < r 2 ⇒ l 1 → l 2 → r 1 → r 2 [l_1,r_1][l_2,r_2],l_1[l1,r1][l2,r2],l1<l2<r1<r2l1l2r1r2

然后对于每个参与点进行限制的 d f s dfs dfs,开始跑每条边的边权。

如果到相同点的边权在取模 2 2 2 意义下不同,说明限制之间出现矛盾。

注意到并不是每个数都在这条链上,i.e. 4 , 6 , 1... 4,6,1... 4,6,1...

这些边只是起将限制紧密联系在一起的作用。

每两个点之间的原区间的方案数是可以通过 d p dp dp 计算的。

d p i , 0 / 1 : dp_{i,0/1}: dpi,0/1: i i i 个数和为 0 / 1 0/1 0/1 (在 ( m o d 2 ) \pmod 2 (mod2) 的意义下)的方案数。

一个数的取值区间 [ l i , r i ] [l_i,r_i] [li,ri],无非可以划分为奇数 x x x 个,偶数 y y y 个。

选奇数会改变奇偶,选偶数则不改变。
d p i , 0 ← d p i − 1 , 0 × y + d p i − 1 , 1 × x d p i , 1 ← d p i − 1 , 0 × x + d p i − 1 , 1 × y dp_{i,0}\leftarrow dp_{i-1,0}\times y+dp_{i-1,1}\times x\\dp_{i,1}\leftarrow dp_{i-1,0}\times x+dp_{i-1,1}\times y dpi,0dpi1,0×y+dpi1,1×xdpi,1dpi1,0×x+dpi1,1×y
没有限制的区间就这么转移,一旦有类似与上面的边限制,去除掉不合法,继续转移即可。

貌似这样转移很困难w(゚Д゚)w


solution

首先, 考虑前缀和, 每个 [ l , r ] [l,r] [l,r] 的限制转化为 l − 1 l-1 l1 r r r 两个前缀和之间的限制。

然后对所有限制进行连边。

每个连通块的奇偶性只有两种选择方法。【并不需要像我那么傻逼地去缩成一条链】

我们可以枚举每个连通块的奇偶性, 然后统计答案。

注意到, 如果一个连通块只有 1 1 1 个数,我们不需要枚举(放到后面去 d p dp dp

因为这个位置的奇偶性不会影响到别的元素。

所以只要在枚举完每个大于等于 2 2 2 的连通块的奇偶性之后, 从右往左做一遍 d p dp dp, 记录一下当前前缀和的奇偶性是多少, 对应的方案数即可。

时间复杂度 O ( 2 n 2 n ) O(2^\frac{n}{2}n) O(22nn)


std

#include 
using namespace std ;

typedef long long LL ;

#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 42 ;
const int mod = 1e9 + 7 ;

char buf[1000000] ;
int len ;
int p[MAXN], c[MAXN] ;
int L[MAXN], R[MAXN], odd[MAXN], even[MAXN] ;
int dp[MAXN][MAXN][2] ;
pair < int, int > nxt[MAXN][MAXN][2] ;
int ans[MAXN], tmp[MAXN] ;
int rt[MAXN], col[MAXN] ;
int idx[MAXN] ;
int use[MAXN] ;
int n, m ;
int T ;

int F ( int x ) { //带权并查集实现前缀和限制
	if ( p[x] == x ) return x ;
	int res = F ( p[x] ) ;
	c[x] ^= c[p[x]] ;
	return p[x] = res;
}

int upd ( int x, int y, int o, int n ) {
	if ( dp[y][x][o] == 0 )
		return 0 ;
	int m = y - x ;
	for ( int i = x ; i <= y ; ++ i ) {
		tmp[i] = nxt[y][i][o].first ;
		o = nxt[y][i][o].second ;
	}
	return 1 ;
}

void up ( int ok ) {
	if ( !ok )
		return ;
	for ( int i = 1 ; i <= n ; ++ i ) {
		if ( tmp[i] > ans[i] )
			return ;
		if ( tmp[i] < ans[i] ) {
			for ( int j = 1 ; j <= n ; ++ j ) {
				ans[j] = tmp[j] ;
			}
			return ;
		}
	}
}

void add ( int x ) {
	if ( x / 10 )
		add ( x / 10 ) ;
	buf[len ++] = x % 10 + '0' ;
}

int main() {
	freopen("parity.in", "r", stdin);
	freopen("parity.out", "w", stdout);
	scanf ( "%d%d", &n, &m ) ;
	for ( int i = 0 ; i <= n ; ++ i ) {
		p[i] = i ;
		c[i] = 0 ;
		use[i] = 0 ;
	}
	for ( int i = 1 ; i <= n ; ++ i ) {
		scanf ( "%d%d", &L[i], &R[i] ) ;
		int tmp = R[i] - L[i] + 1 ; 
		//计算i可选范围内奇数和偶数的个数
		odd[i] = tmp / 2 + ( tmp % 2 == 1 && R[i] % 2 == 1 ) ;
		even[i] = tmp / 2 + ( tmp % 2 == 1 && R[i] % 2 == 0 ) ;
	}
	for ( int i = 1 ; i <= n ; ++ i ) {
		for ( int j = 1 ; j <= n + 1 ; ++ j ) {
			dp[i][j][0] = dp[i][j][1] = 0 ;
			nxt[i][j][0] = make_pair ( mod, mod ) ;
			nxt[i][j][1] = make_pair ( mod, mod ) ;
		}
	}
	//dp[l][r][0/1]:计算[l,r]区间内数的奇偶性
	for ( int i = 1 ; i <= n ; ++ i ) {
		dp[i][i + 1][0] = 1 ;
		for ( int j = i ; j >= 1 ; -- j ) {
			dp[i][j][0] = ( 1LL * dp[i][j + 1][1] * odd[j] + 1LL * dp[i][j + 1][0] * even[j] ) % mod ;
			dp[i][j][1] = ( 1LL * dp[i][j + 1][0] * odd[j] + 1LL * dp[i][j + 1][1] * even[j] ) % mod ;
		}
		for ( int j = 1 ; j <= i ; ++ j ) {
			if ( dp[i][j + 1][1] && odd[j] ) {
				nxt[i][j][0] = min ( nxt[i][j][0], make_pair ( L[j] + ( L[j] % 2 == 0 ), 1 ) ) ;
			}
			if ( dp[i][j + 1][0] && even[j] ) {
				nxt[i][j][0] = min ( nxt[i][j][0], make_pair ( L[j] + ( L[j] % 2 == 1 ), 0 ) ) ;
			}
			if ( dp[i][j + 1][0] && odd[j] ) {
				nxt[i][j][1] = min ( nxt[i][j][1], make_pair ( L[j] + ( L[j] % 2 == 0 ), 0 ) ) ;
			}
			if ( dp[i][j + 1][1] && even[j] ) {
				nxt[i][j][1] = min ( nxt[i][j][1], make_pair ( L[j] + ( L[j] % 2 == 1 ), 1 ) ) ;
			}
		}
	}
	int ok = 1 ;
	for ( int i = 1 ; i <= m ; ++ i ) {
		int u, v, w, x, y ;
		scanf ( "%d%d%d", &u, &v, &w ) ;
		-- u ;
		x = F ( u ) ;
		y = F ( v ) ;
		use[u] = use[v] = 1 ;
		if ( x != y ) {
			if ( x < y ) swap ( x, y ) ;
			p[x] = y ;
			c[x] = ( c[u] - c[v] + 2 + w ) % 2 ;
		} else if ( ( c[u] ^ c[v] ) != w ) ok = 0 ;
	}
	if ( !ok ) { //限制冲突 无解-1
		buf[len ++] = '0' ;
		buf[len ++] = '\n' ;
		buf[len ++] = '-' ;
		buf[len ++] = '1' ;
		buf[len ++] = '\n' ;
		return 0;
	}
	int cnt = 0, tot = 0, uns = 0;
	for ( int i = 0 ; i <= n ; ++ i )
		if ( use[i] ) {
			if ( F ( i )  == i )
				rt[cnt ++] = i;
			idx[tot ++] = i;
		} else
			uns++;
	int res = 0, OK = 0 ;
	for ( int i = 1 ; i <= n ; ++ i )
		ans[i] = mod ;
	for ( int s = 0 ; s < 1 << cnt ; ++ s ) {
		if ( cnt && rt[0] == 0 && s % 2 )
			continue ;
		col[0] = 0 ;
		for ( int i = 0 ; i < cnt ; ++ i ) {
			col[rt[i]] = s >> i & 1 ;
		}
		int ans = 1, j = 0, ok = 1 ;
		for ( int o = ( rt[0] == 0 ) ; o < tot ; ++ o ) {
			int i = idx[o], x = p[i] ;
			if ( x != i )
				col[i] = col[x] ^ c[x] ^ c[i] ;
			ans = 1LL * ans * dp[i][j + 1][col[j] ^ col[i]] % mod ;
			ok &= upd ( j + 1, i, col[j] ^ col[i], j ) ;
			if ( !ok )
				break ;
			j = i ;
		}
		if ( ok && j < n ) {
			ans = 1LL * ans * ( dp[n][j + 1][0] + dp[n][j + 1][1] ) % mod ;
			int a = upd ( j + 1, n, 0, j ) ;
			up ( ok && a ) ;
			int b = upd ( j + 1, n, 1, j ) ;
			up ( ok && b ) ;
			ok &= a | b ;
		} else
			up ( ok ) ;
		if ( ok )
			OK = 1 ;
		res = ( res + ans ) % mod ;
	}
	if ( !OK ) {
		buf[len ++] = '0' ;
		buf[len ++] = '\n' ;
		buf[len ++] = '-' ;
		buf[len ++] = '1' ;
		buf[len ++] = '\n' ;
	} else {
		add ( res ) ;
		buf[len ++] = '\n' ;
		for ( int i = 1 ; i <= n ; ++ i ) {
			add ( ans[i] ) ;
			buf[len ++] = i < n ? ' ' : '\n' ;
		}
	}
	buf[len] = 0;
	printf("%s", buf);
}

你可能感兴趣的:(#,DP,算法,带权并查集,前缀和优化,dp)