LOJ 2977 「THUSCH 2017」巧克力
神仙题QaQ
做法是给每种颜色随机分配一个 $ 1 $ 到 $ k $ 的颜色,然后跑一次斯坦纳树,得到当前包含至少 $ k $ 种颜色的最小联通块。
我们考虑什么时候我们可以随机到答案?如果答案的 $ k $ 颜色恰好本随机分配到了不同的颜色,那么跑出来的答案肯定就是正确的。所以说,一次随机的正确率大概是 $ \frac{k!}{k^k} $ 大概是 $ 0.3% $ 。所以我们随机大概 200 次,正确率就远超 100 了。
另外考虑中位数尽量小这个限制,我们二分这个中位数,考虑怎么 check ,当前我们的第一限制是块数,第二限制是拿到的大于中位数的尽可能少。由于我们要让 $ k $ 种联通,最多要选的块也不会超过 $ 5 \times (233 + 233) $ 不会超过 2000 多,实际上还肯定比这个小得多,保险起见(其实是方便)直接把大于中位数设置为 $ 10001 $ ,小于中位数 $ 9999 $ 。然后最后块数就是 $ \lfloor \frac{ans + 2500}{10000} \rfloor $ ,这样就可以方便的验证了。
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "vector"
#include "queue"
using namespace std;
int dirx[4] = { 0,0,-1,1 };
int diry[4] = { 1,-1,0,0 };
#define chkmn( a , b ) ( (a) > (b) ? ( (a) = (b) , 1 ) : 0 )
#define MAXN 256
int n , m , k , s;
int c[MAXN][MAXN] , A[MAXN][MAXN] , w[MAXN][MAXN];
int col[MAXN] , en , rc[MAXN];
int dp[MAXN][MAXN][1<<5];
#define pii pair
#define fi first
#define se second
#define mp make_pair
queue Q;
int vis[MAXN][MAXN];
void spfa( int s ) {
while( !Q.empty() ) {
int x = Q.front().fi , y = Q.front().se; Q.pop();
vis[x][y] = 0;
for( int d = 0 ; d < 4 ; ++ d ) {
int X = x + dirx[d] , Y = y + diry[d];
if( X < 1 || X > n || Y < 1 || Y > m || c[X][Y] == -1 ) continue;
if( chkmn( dp[X][Y][s] , dp[x][y][s] + w[X][Y] ) && !vis[X][Y] )
Q.push( mp( X , Y ) ) , vis[X][Y] = 1;
}
}
}
int work( ) {
int ans = 0x3f3f3f3f;
for( int t = 1 ; t <= 200 ; ++ t ) {
random_shuffle( col + 1 , col + 1 + s );
for( int i = 1 ; i <= s ; ++ i ) rc[col[i]] = i % k;
for( int i = 1 ; i <= n ; ++ i )
for( int j = 1 ; j <= m ; ++ j ) {
for (int st = 0; st < (1 << k); ++st) dp[i][j][st] = 0x3f3f3f3f;
if( ~c[i][j] ) dp[i][j][1<> T;
while( T --> 0 ) {
cin >> n >> m >> k;
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j )
scanf("%d",&c[i][j]) , col[++ en] = c[i][j];
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j )
scanf("%d",&A[i][j]);
sort( col + 1 , col + 1 + en );
s = unique( col + 1 , col + 1 + en ) - col - 1;
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j ) w[i][j] = 1;
res = work( );
if( res > 1e9 ) { puts("-1 -1"); continue; }
int l = 0 , r = 1e6;
while( l <= r ) {
int mid = l + r >> 1;
for( int i = 1 ; i <= n ; ++ i ) for( int j = 1 ; j <= m ; ++ j )
w[i][j] = ( A[i][j] <= mid ? 9999 : 10001 );
int re = work( );
if( re <= ( re + 2500 ) / 10000 * 10000 ) r = mid - 1;
else l = mid + 1;
}
printf("%d %d\n",res,l);
}
}