Loading

matrix

\(link\)
不得不说是一道神题
先说暴力状压作法,枚举他转移到的地方,记录没列选没选过就行了。

code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <iostream>
#define int long
#define R register int
#define printf Ruusupuu = printf

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
const int N = 18 ;

int Ruusupuu ;

inline int lb( int x ){ return x & -x ; }
inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
	return fg ? -w : w ;
}

int n , m , k , l [N] , r [N] , ans , f [N][1 << N] , lg [40] ;

void sc(){
	n = read() , m = read() ;
	for( R i = 1 ; i <= n ; i ++ ) l [i] = read() , r [i] = read() ; 
}


inline void debug( int x ){ cout << bitset< 10 > ( x ) << endl ; }

void work(){
	for( R i = 0 ; i < 36 ; i ++ ) lg[( 1ll << i ) % 37] = i ;
	f [0][0] = 1 ;
	for( R i = 0 ; i <= n ; i ++ ){
		for( R j = 0 ; j < 1 << m ; j ++ ){
			if( !f [i][j] ) continue ;
			for( R k = 1 ; k <= l [i + 1] ; k ++ ){						
				for( R s = r [i + 1] ; s <= m ; s ++ ){
					if( ( ( j >> ( k - 1 ) ) & 1 ) || ( ( j >> ( s - 1 ) ) & 1 ) ) continue ;
					int t = j | ( 1 << ( k - 1 ) ) ;
					t |= ( 1 << ( s - 1 ) ) ;
					f [i + 1][t] += f [i][j] ; 				
			//		printf( "%ld %ld\n" , i , f [i][j] ) ; debug( j ) ; debug( t ) ;
				}			
			}
		}
		if( i == n ) for( R j = 0 ; j < 1 << m ; j ++ ) ans += f [i][j] ; 
	} printf( "%ld\n" , ans ) ; 
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
}

正解按列\(dp\),他的状态定义特别神。
\(f[i][j]\)表示在第\(i\)列,在右侧区间放了\(j\)个1的方案数。
有一个事情,就是你想在右侧区间放1,那么首先右侧区间的左端点要在\(i\)左边
然后开始转移
如果在第\(i\)列不在右区间放1

\[f[i][j]+=f[i-1][j] \]

如果放

\[f[i][j]+=f[i-1][j-1]*(rr[i]-j+1) \]

其中\(rr[i]\)代表在第\(i\)列,有多少右区间的的左端点在\(i\)左边,这个很好实现,一个前缀和就行了。
在第\(i\)列上放1,一共有\(rr[i]-j+1\)行支持这个操作,因为已经放了\(j-1\)行了。

这个状态定义最神的地方在于他不定义左区间相关内容,因为左右区间有可能相互干扰,所以他干脆就不定义。
每当到达一列,如果这一列是左区间的右端点,再考虑在这个做区间放1
这个需要我们对左端点前缀和,设为\(ll[i]\)表示在第\(i\)点和之前有多少个左端点。

那么,到达第\(i\)列,有\(ll[i]-ll[i-1]\)个区间的左端点在第\(i\)列上,我们要将这些左区间填上1(不然之后就没机会填1了)
我现在需要\(ll[i]-ll[i-1]\)列来放下这些1,我还有\(i-j-ll[i-1]\)列,因为各个之间的顺序不同会导致最终方案不同排列一下就行了。
\(f[i][j]*=A_{i-j-ll[i-1]}^{ll[i]-ll[i-1]}\)

回想一下这道题,他通过这个状态定义,成功化解了左右区间相互干扰的问题。
通过只统计当前列左边的右区间并记录,只在第\(i\)列上看放不放1,消除了在左区间放对他的干扰,同时给左区间提供了信息。
通过只到左区间的右端点在放1,保证了可以利用上右端点占用了多少列的信息,同时不用把它装到状态定义中,保证了时空复杂度。
不得不说真的神。

code


#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long
#define R register int
#define printf Ruusupuu = printf

using namespace std ;
typedef long long L ;
typedef long double D ;
typedef unsigned long long G ;
const int N = 3e3 + 10 ;
const int M = 998244353 ;

int Ruusupuu ;

inline int read(){
	int w = 0 ; bool fg = 0 ; char ch = getchar() ;
	while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
	while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ '0' ) , ch = getchar() ;
	return fg ? -w : w ;
}

inline int T( int a , int b ){ return 1ll * a * b % M ; }
inline int J( int a , int b ){ return a + b >= M ? a + b - M : a + b ; }

int n , m , r [N] , l [N] , f [N][N] , ll [N] , rr [N] , js [N] , inv [N] ;

void sc(){
	n = read() , m = read() ;
	for( R i = 1 ; i <= n ; i ++ ) l [i] = read() , ll [l [i]] ++ , r [i] = read() , rr [r [i]] ++ ;//, printf( "%ld %ld\n" , i , n ) ;
	for( R i = 1 ; i <= m ; i ++ ) ll [i] += ll [i - 1] , rr [i] += rr [i - 1] ;//, printf( "LR%ld %ld\n" , ll [i] , rr [i] ) ;
}

inline int A( int n , int m ){
	return T( js [n] , inv [n - m] ) ;
}

void work(){
	f [0][0] = 1 ; js [0] = js [1] = inv [0] = inv [1] = 1 ;
	for( R i = 2 ; i < N ; i ++ ) js [i] = T ( js [i - 1] , i ) , inv [i] = T ( ( M - M / i ) , inv [M % i] ) ;//, printf( "%ld %ld\n" , js [i] , inv [i] ) ;
	for( R i = 1 ; i < N ; i ++ ) inv [i] = T ( inv [i - 1] , inv [i] ) ;//, printf( "%ld\n" , inv [i] ) ; 
	for( R i = 1 ; i <= m ; i ++ ){ //dp每一列
		for( R j = 0 ; j <= min ( i , n ) ; j ++ ){
			if( !j ) f [i][j] = f [i - 1][j] ;
			else f [i][j] = J ( f [i - 1][j] , T ( f [i - 1][j - 1] , max( 0l , ( rr [i] - j + 1 ) ) ) ) ;
			f [i][j] = T( f [i][j] , A( i - j - ll [i - 1] , ll [i] - ll [i - 1] ) ) ;
//			printf( "%ld %ld %ld\n" , i , j , f [i][j] ) ;
		}
	} printf( "%ld\n" , f [m][n] ) ;
}

signed main(){	
	sc() ;
	work() ;
	return 0 ;
}

posted @ 2021-06-09 07:44  Soresen  阅读(115)  评论(0)    收藏  举报