模拟33—「Hunter·Defence·Connect」
Hunter
正解很简洁,然而没想到。
期望的线性性展开题目所求,可以发现就是其他猎人死在他前面的概率求和。
然后因为要求他什么时候死所以再加一个1。
其他猎人死在他前面的概率就是 \(\frac{W_i}{W_1+W_i}\)
期望的题无非就是dp或者线性展开后dp或直接求
dp实在想不出来怎么转移可以考虑直接求
Defence
考场上想到转化和线段树合并了,也调试成功了,但是有一个小细节没注意到。
就是中间最长的长度还要和左边最长的长度+右边最长的长度去取max。
我真的是sb,其实对拍的时候写暴力的时候一定要多想点,不要按着正解的思路去写暴力,否则只能检查你线段树写没写对,不能检查你这题写没写对。
Connect
考场想了个乱搞
就是状压出来一个1~n的联通块,在联通块内求最大生成树。
再用dfs染色出来其他联通块。
其他联通块随便连边,和状压联通块每个其他联通块只能和上面的一个点连边。
虽然具体思路不太对,但是性质还是对的。
这种图论题一定要多画画,多画几个例子就知道有什么性质了。
正解是利用上面的性质打了个状压dp
状态定义 \(f_{s,u}\) 表示点集为 s ,当前考虑的链的末端为 u 的时候的最小代价。
按照上面的性质,有两个操作
- 加进去一个点v,把v变成链的末尾,要求是v和s的除了(u,v)的所有连边都要去掉
\[\large f_{s,u}+calc(s,v)\rightarrow f_{s|\{v\},v}
\]
- 加进去一坨点,对应和状压联通块上最多一个点连边
\[\large f_{s,u}+calc(s,\{l\})\rightarrow f_{s|\{l\},u}
\]
然后复杂度是 \(O(3^nnm)\)的,并不对,所以需要把 \(calc\)预处理出来,复杂度就变成 \(O(3^nn^2)\) 了
第一次学到枚举子集的正确打开方式。
for( R T = S ; T ; T = ( ( T - 1 ) & S ) )
//T是S的子集
粘一个代码方便理解。
code
#include <cstdio>
#include <bitset>
#include <cstring>
#include <iostream>
#include <assert.h>
#include <algorithm>
#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 ;
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 , x , y , z , f [1 << 16][16] , lg [1 << 21] , ans = 1e9 , calcs [1 << 16][16] , Y [1 << 16][16] ;
int fr [N << 1] , to [N << 1] , net [N << 1] , head [N] , cnt = 1 , w [N] ;
#define add( f , t ) fr [++ cnt] = f , to [cnt] = t , net [cnt] = head [f] , head [f] = cnt , w [cnt] = z
void sc(){
n = read() , m = read() , memset( head , -1 , sizeof( head ) ) ;
for( R i = 1 ; i <= m ; i ++ ) x = read() , y = read() , z = read() , add( x , y ) , add( y , x ) ;
}
inline void debug( int x ){ cout << bitset<10>(x) << endl ; }
inline int lb( int x ){ return x & -x ; }
inline int calc( int x , int p ){
int ans = 0 ;
for( R i = head [p] ; ~i ; i = net [i] )
if( ( 1 << ( to [i] - 1 ) ) & x ) ans += w [i] ;
return ans ;
}
inline int Calc( int x , int p ){
int ans = 0 ;
while( p ){
int j = lg [lb( p )] + 1 ;
ans += calcs [x][j] ;
p -= lb( p ) ;
}
return ans ;
}
void work(){
for( R i = 1 ; i < ( 1 << n ) ; i ++ )
for( R j = 1 ; j <= n ; j ++ )
calcs [i][j] = calc( i , j ) ;
for( R i = 1 ; i <= n ; i ++ ){
for( R j = head [i] ; ~j ; j = net [j] ){
for( R k = 1 ; k < ( 1 << n ) ; k ++ )
if( ( 1 << ( to [j] - 1 ) ) & k ) Y [k][i] += w [j] ;
}
}
lg [0] = -1 ;
for( R i = 1 ; i < ( 1 << n ) ; i ++ ) lg [i] = lg [i / 2] + 1 ;
memset( f , 0x3f , sizeof( f ) ) , f [1][1] = 0 ;
for( R i = 1 ; i < ( 1 << n ) ; i ++ ){
R j = i , p = ( i ^ ( ( 1 << n ) - 1 ) ) ;
while( j ){
R k = lg [lb( j )] + 1 ;
for( R l = head [k] ; ~l ; l = net [l] )
if( i & ( 1 << ( to [l] - 1 ) ) ) continue ;
else f [i | ( 1 << ( to [l] - 1 ) )][to [l]] = min( f [i | ( 1 << ( to [l] - 1 ) )][to [l]] , f [i][k] + calcs[i][to [l]] - w [l] ) ;
for( R l = p ; l ; l = ( ( l - 1 ) & p ) )
f [i | l][k] = min( f [i | l][k] , f [i][k] + Calc( i , l ) - Y [l][k] ) ;
j -= lb( j ) ;
}
} printf( "%d\n" , f [( 1 << n ) - 1][n] ) ;
}
signed main(){
sc() ;
work() ;
return 0 ;
}
总结
这场又炸了,原因如下
- T1没有复习OI宝典,导致忘记取模,挂了35pts
- T2被很水的样例骗了,差一个小细节没想,导致错误的std拍错误的程序,要是想想细节就 9->100。
- T3就差几行没打完(虽然是假的,还有一个细节不太对),80->0
225->19
亿些收获
- T1我认为能推出来,然而还是高估了自己,主要是卡在状态定义上没出来,一直想推个柿子,没想到概率和期望常用套路线性展开,自己概率和期望还是太弱。
- T1不知道为什么dfs和状压都打了,浪费了写完T3的时间,以后状压写的还是要自信点。
- T2细节想少了,没想到左右还能有贡献,所以挂没了,这种题必须要先理解完,如果样例水,一定要多想点。
- T3乱搞考场想的,虽然我知道这么麻烦一看就不是正解,但是还是打了,实际上也不是很难调,分数也不是很少,其实这种乱到一定下程度上的乱搞可以一打,因为出题人可能都想不到你能搞得这么乱。
- T3正解很神仙,各种dp只要状态定义一麻烦(就是不能直接从题里面看出来的那种),我几乎就想不到,其实也没往dp想,dp巨弱,考场上剩出来时间可以考虑给不以为是dp的题随便乱搞几个dp定义,没准会有额外的收获。
- 这场虽然没打出来多少,但是推了几个性质,确实推性质,转化题意比较重要,只有思考才能进步。
$The \ light \ has \ betrayed \ me$

浙公网安备 33010602011771号