模拟29—「最长不下降子序列·完全背包问题·最近公共祖先」
最长不下降子序列
打表发现明显有规律,就是他有周期性。
这种递推数列必定有不超过模数长的周期,证明的话,直接搬运
考试的时候算法假了没调试出来,当时只认为给前面乱的部分和后面不够一个周期的部分各加一个完整的周期,然后一跑就完了。
但是是错的,因为他让求的是不降子序列,所以如果循环节是这样的
34567 34567 34567 34567
其实他能这么选
34567 34567 34567 34567
这样就需要我们给前面或者后面配上周期长度个周期,然后暴力跑一遍,最后直接大块加上就行了。
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
#define R register int
#define scanf Ruusupuu = scanf
#define freopen rsp_5u = freopen
int Ruusupuu ;
FILE * rsp_5u ;
using namespace std ;
typedef long long L ;
typedef double D ;
const int N = 1e5 + 10 ;
const int K = 9e4 + 10 ;
inline void of(){ freopen( "in.in" , "r" , stdin ) , freopen( "out.out" , "w" , stdout ) ; }
inline void cf(){ fclose( stdin ) , fclose( stdout ) ; }
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 , a [K] , b [K] , A , B , C , M , T , topre , topsc , vis [K] , ex , f [K] , g [K] ;
void sc(){
n = read() , a [1] = read() , vis [a [1]] = 1 , A = read() , B = read() , C = read() , M = read() ;
}
void work(){
if( n == 1 ) printf( "%lld\n" , 1ll ) , exit( 0 ) ;
for( R i = 2 ; i <= K ; i ++ ){
a [i] = ( A * a [i - 1] * a [i - 1] + B * a [i - 1] + C ) % M ;
if( vis [a [i]] ){ T = i - vis [a [i]] , topre = i - 1 ; break ; }
vis [a [i]] = i ;
}
int uk = topre ;
for( R i = 1 ; i <= T ; i ++ )
for( R j = 1 ; j <= T ; j ++ )
a [++ uk] = a [j + topre - T] ;
topre = uk ;
int k = ( ( n - topre ) % T ) + T ;
for( R i = 1 ; i <= T ; i ++ )
b [i] = a [i + topre - T] ;
for( R i = T + 1 ; i <= k ; i ++ )
b [i] = b [i % T] ;
ex = ( n - topre - k ) / T ;
for( R i = 1 ; i <= topre ; i ++ ){
for( R j = i - 1 ; j >= 1 ; j -- )
if( a [j] <= a [i] ) f [i] = max( f [j] , f [i] ) ;
f [i] ++ ;
}
for( R i = k ; i >= 1 ; i -- ){
for( R j = i + 1 ; j <= k ; j ++ )
if( b [j] >= b [i] ) g [i] = max( g [i] , g [j] ) ;
g [i] ++ ;
}
int ans = 0 ;
for( R i = 1 ; i <= T ; i ++ )
ans = max( ans , f [i + topre - T] + g [i] ) ;
printf( "%lld\n" , ans + ex ) ;
}
signed main(){
// of() ;
sc() ;
work() ;
// cf() ;
return 0 ;
}
完全背包问题
这道题考场是以为要对 \(c\) 折半搜索,但是不太会,所以暂时没做,然后就没做。
其实正解我在那么一刹那也想过,只不过没想下去。
首先分类讨论,若所有体积都大于 \(l\) ,那么说明我们最多选择 \(c\) 个物品,背包体积变成了 \(cV\) , 其实就是一个二维背包必须填满的模板,复习背包九讲就可以得到做法。
那么如果我们设计 \(f_{i,j,k}\) 表示前 \(i\)个物品选择了 \(j\) 个体积为 \(k\) 是否可行。
转移方程容易列出 \(f_{i,j,k}=f_{i-1,j,k}|f_{i,j-1,k-v[i]}\)
但是我们发现状态最多有 \(4.5e8\)种,很炸。
所以我们可以用 \(bitset\) 优化一下,把最后一维压到 \(bitset\) 里面。
所以枚举 \(cV\) 变成了直接或一下,时间空间全部除以 \(32\) ,就可以接受了。
如果有大于 \(l\) 也有小于 \(l\) 的,那么最小的体积一定小于 \(l\) ,所以我们只需要记录 \(f_{i,j,k}\) 为前 \(i\) 个物品选择了 \(j\) 个 体积模 \(v_{min}\) 等于 \(k\) 的情况下所用体积的最小值。
所以我们只要转移出来,对于一个 \(W\) , 枚举选择了几个大体积物品,对于一个 \(f [n][i][W \% v_{min}]\) 是否小于等于 \(W\) 。
如果 \(W\) 大于它就是不合法的。
所以列出来式子。
发现第二个式子转移成环,没有合适的枚举顺序,所以把它建一个图,跑一个最短路就行了。
具体实现就是设置一个原点 \(s\) , \(dis_s= 0\) , 然后给每个点都和原点连一个权值为 \(f_{i-1,j,k}\) 的边,代表他的初始值,然后发现一个状态能 \(u\) 转移到状态 \(v\) ,就在\(u ,v\)之间建一条有向边,权值为 \(V_i\)
对于成环的转移,若是加和可以用高斯消元解出来,若是取最值可以用最短路跑出来。
code
#include <bitset>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define R register int
#define int long long
#define scanf Ruusupuu = scanf
#define freopen rsp_5u = freopen
int Ruusupuu ;
FILE * rsp_5u ;
using namespace std ;
typedef long long L ;
typedef double D ;
const int N = 2e5 + 10 ;
const int V = 3e5 + 10 ;
const int S = 1e4 + 10 ;
inline void of(){ freopen( "in.in" , "r" , stdin ) , freopen( "out.out" , "w" , stdout ) ; }
inline void cf(){ fclose( stdin ) , fclose( stdout ) ; }
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 , v [N] , l , c , w , g [52][32][S] , dis [N] , q [N] , hh , tt , z ;
bitset <V> f [31][51] ;
bool vis [N] ;
//bitset优化,不用枚举V,同时时间空间全部除以32
int fr [N << 1] , to [N << 1] , net [N << 1] , head [N] , cnt = 1 , ww [N << 1] ;
#define add( f , t ) fr [++ cnt] = f , to [cnt] = t , net [cnt] = head [f] , head [f] = cnt , ww [cnt] = z
void onnea(){
f [0][0][0] = 1 ;
for( R i = 1 ; i <= n ; i ++ ) for( R j = 0 ; j <= c ; j ++ ){
f [i][j] = f [i - 1][j] ;
if( j ) f [i][j] |= ( f [i][j - 1] << v [i] ) ;
}
while( m -- ){
w = read() ;
bool fg = 0 ;
for( R i = 0 ; i <= c ; i ++ )
if( f [n][i][w] ){ puts( "Yes" ) ; fg = 1 ; break ; }
if( !fg ) puts( "No" ) ;
}
}
void sc(){
n = read() , m = read() ;
for( R i = 1 ; i <= n ; i ++ ) v [i] = read() ;
sort( v + 1 , v + 1 + n ) ;
l = read() , c = read() ;
}
inline void buildgraph( int ii , int jj ){
int s = v [1] ;
memset( head , -1 , sizeof( head ) ) , cnt = 1 ;
for( R i = 0 ; i < v [1] ; i ++ ){
z = g [ii - 1][jj][i] , add( i , s ) , add( s , i ) ;
z = v [ii] , add( i , ( i + v [ii] ) % v [1] ) ;
}
}
void spfa( int ii , int jj ){
buildgraph( ii , jj ) , q [hh = tt = 1] = v [1] ;
memset( dis , 0x3f , sizeof( dis ) ) , dis [v [1]] = 0 ;
while( hh <= tt ){
int x = q [hh ++] ; vis [x] = 0 ;
for( R i = head [x] ; ~i ; i = net [i] ){
int y = to [i] ;
if( dis [y] > dis [x] + ww [i] ){
dis [y] = dis [x] + ww [i] ;
if( !vis [y] ) vis [y] = 1 , q [++ tt] = y ;
}
}
}
for( R i = 0 ; i < v [1] ; i ++ ) g [ii][jj][i] = dis [i] ;
}
void work(){
if( l <= v [1] ) return onnea() , void() ;
memset( g , 0x3f , sizeof( g ) ) , g [0][0][0] = 0 ;
for( R i = 1 ; i <= n ; i ++ ) for( R j = 0 ; j <= c ; j ++ ){
if( v [i] >= l && j )
for( R k = 0 ; k < v [1] ; k ++ )
g [i][j][k] = min( g [i - 1][j][k] , g [i][j - 1][( ( k - v [i] ) % v [1] + v [1] ) % v [1]] + v [i] ) ;
else if( v [i] >= l && !j )
for( R k = 0 ; k < v [1] ; k ++ )
g [i][j][k] = g [i - 1][j][k] ;
else if( v [i] < l ) spfa( i , j ) ;
}
while( m -- ){
w = read() ;
bool fg = 0 ;
for( R i = 0 ; i <= c ; i ++ )
if( w >= g [n][i][w % v [1]] ){ puts( "Yes" ) ; fg = 1 ; break ; }
if( !fg ) puts( "No" ) ;
}
}
signed main(){
// of() ;
sc() ;
work() ;
// cf() ;
return 0 ;
}
最近公共祖先
考场上一开始打了个假做法,过不了大样例直接裂开。
然后想到爆跳父亲到根之后修改儿子,但是没有调试成功。
正解就是优化了一下暴力,因为修改儿子这个操作最多进行两次。
所以我们记录一下一个点是否已经被修改过两次,是否被修改过一次,若是一次,从哪个儿子修改的。
如果(修改过一次并且是这个儿子)||(修改过两次) ,我们就可以直接跳出爆跳父亲的循环,因为一个点若被修改过,他的父亲肯定也被修改过。
所以就做完了。
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define R register int
#define int long long
#define scanf Ruusupuu = scanf
#define freopen rsp_5u = freopen
int Ruusupuu ;
FILE * rsp_5u ;
using namespace std ;
typedef long long L ;
typedef double D ;
const int N = 1e5 + 10 ;
inline void of(){ freopen( "in.in" , "r" , stdin ) , freopen( "out.out" , "w" , stdout ) ; }
inline void cf(){ fclose( stdin ) , fclose( stdout ) ; }
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 ;
}
char s [20] ;
int n , m , x , y , w [N] , son [N] , two [N] ;
int fr [N << 1] , to [N << 1] , net [N << 1] , head [N] , cnt = 1 ;
#define add( f , t ) fr [++ cnt] = f , to [cnt] = t , net [cnt] = head [f] , head [f] = cnt
int bs [N] , dep [N] , size [N] , fa [N] ;
int fn [N] , top [N] , rl [N] , knt ;
void sc(){
n = read() , m = read() , memset( head , -1 , sizeof( head ) ) ;
for( R i = 1 ; i <= n ; i ++ ) w [i] = read() ;
for( R i = 1 ; i < n ; i ++ ) x = read() , y = read() , add( x , y ) , add( y , x ) ;
}
void dfs1( int x , int Fa ){
for( R i = head [x] ; ~i ; i = net [i] ){
int y = to [i] ;
if( y == Fa ) continue ;
dep [y] = dep [x] + 1 , fa [y] = x ;
dfs1( y , x ) , size [x] += size [y] ;
if( size [y] > size [bs [x]] ) bs [x] = y ;
} size [x] ++ ;
}
void dfs2( int x , int tops ){
top [x] = tops , fn [x] = ++ knt , rl [knt] = x ;
if( !bs [x] ) return ;
dfs2( bs [x] , tops ) ;
for( R i = head [x] ; ~i ; i = net [i] ){
int y = to [i] ;
if( y == fa [x] || y == bs [x] ) continue ;
dfs2( y , y ) ;
}
}
struct T{ int l , r , data , lz ; } t [N << 2] ;
int lx , rx , dlt , pos , f [N] ;
inline void ud( int x ){ t [x].data = max( t [x << 1].data , t [x << 1 | 1].data ) ; }
inline void atg( int x , int dlt ){ t [x].lz = max( t [x].lz , dlt ) , t [x].data = max( t [x].data , dlt ) ; }
inline void sp( int x ){ atg( x << 1 , t [x].lz ) , atg( x << 1 | 1 , t [x].lz ) , t [x].lz = 0 ; }
void build( int x , int l , int r ){
t [x].l = l , t [x].r = r ;
if( l == r ) return ;
int mid = ( l + r ) >> 1 ;
build( x << 1 , l , mid ) ;
build( x << 1 | 1 , mid + 1 , r ) ;
}
void upd( int x ){
if( t [x].l >= lx && t [x].r <= rx ) return atg( x , dlt ) , void() ;
sp( x ) ;
int mid = ( t [x].l + t [x].r ) >> 1 ;
if( lx <= mid ) upd( x << 1 ) ;
if( rx > mid ) upd( x << 1 | 1 ) ;
ud( x ) ;
}
int ask( int x ){
if( t [x].l == t [x].r ) return t [x].data ;
sp( x ) ;
int mid = ( t [x].l + t [x].r ) >> 1 ;
if( pos <= mid ) return ask( x << 1 ) ;
else return ask( x << 1 | 1 ) ;
}
void ASK(){
pos = fn [x] ;
int ans = ask( 1 ) ;
printf( "%lld\n" , ( ( ans ) ? ans : -1 ) ) ;
}
void UPD(){
if( two [x] ) return ;
lx = fn [x] , rx = fn [x] + size [x] - 1 ;
dlt = w [x] , upd( 1 ) ;
int Fa = fa [x] , last = x ;
while( Fa ){
if( two [Fa] || son [Fa] == last ) break ;
if( son [Fa] && son [Fa] != last ) two [Fa] = 1 ;
else if( !son [Fa] ) son [Fa] = last ;
dlt = w [Fa] ;
lx = fn [Fa] , rx = fn [last] - 1 , upd( 1 ) ;
lx = fn [last] + size [last] , rx = fn [Fa] + size [Fa] - 1 , upd( 1 ) ;
Fa = fa [Fa] , last = fa [last] ;
}
}
inline void debug(){
for( R i = 1 ; i <= 2 * n ; i ++ )
printf( "L%lld R%lld DA%lld LZ%lld\n" , t [i].l , t [i].r , t [i].data , t [i].lz ) ;
}
void work(){
dfs1( 1 , 1 ) , dfs2( 1 , 1 ) , build( 1 , 1 , n ) ;
while( m -- ) scanf( "%s" , s ) , x = read() , ( s [0] == 'Q' ) ? ASK() : UPD() ;
}
signed main(){
// of() ;
sc() ;
work() ;
// cf() ;
return 0 ;
}
总结
这场考试只有十分。
但是收获还是很大的。
之前立的\(flag\)打满暴力在做题果然又倒了。
先说技巧收获。
- \(\mathrm{T1}\)拍出来哪里错了,就应该慢慢调,考场上一直调试没有用的,导致没有调出来,还有就是打的暴力用 \(namespace\) 分装起来,直接把整个代码粘过去就行。
- \(\mathrm{T2}\)暴力有三十分,但是考场上没想到一个沙雕步骤导致我认为只有十分就没写,部分分要思考十分钟左右,并且是只对着那第一档或者第二档想。
- \(\mathrm{T3}\)打之前没有想好最终用数据\(hack\)掉之后不要炸心态,其实写也就写了二三十分钟,但是心态一炸就会浪费好多时间,打暴力也要带脑子,如果你没带脑子打暴力(看错题),你就会体验到错误+错误=正确的对拍结果。
再说知识收获
- \(\mathrm{T2}bitset\)优化布尔类型的\(dp\) ,最短路解决有环形的最值 \(dp\),高斯消元解决有环形的加减 \(dp\)
- \(\mathrm{T3}\) 想法如果时间不对思考如何剪枝,主要就是看哪些东西被重复计算。
再次立\(flag\)
-
带脑子打暴力,给暴力\(10-15min\) 思考时间,对着第一档或者第二档想。
-
打完暴力用 \(namespace\) 装到要交上去的程序里面。
-
想到正解不要有心态波动,不然被 \(hack\) 掉很淦。
-
被拍掉之后对着数据慢慢调试,不要重复做没用的东西。


浙公网安备 33010602011771号