A SPOJ 13041. The Black Riders(二分+最大流/二分匹配)
题意 : 有 n 个霍比特人 , m个洞穴 , 给出了每个霍比特人到每个洞穴的时间。并且每个霍比特人到达洞穴之后可以挖C时间,再挖出一个洞穴来容纳另一个霍比特人,但只能挖一次,也就是说每个洞穴最多就只能容纳两个霍比特人。问要保证K个霍比特人到达洞穴,最少需要多少时间。
思路 : 读完题目就感觉是二分+最大流,不过没想明白怎样保证第二个霍比特人进入挖出来的洞的时候,挖洞的那个霍比特人一定要进入这洞穴。想了半天还是没有建出图。其实想的方向完全就错了,其实当一个霍比特人有时间挖洞的时候,只要重新弄一个新的洞给他就行了,当匹配的时候有人来抢原先的洞的时候,只要进入挖的那个洞就行了。
所以建图的话 ,对于一个霍比特人 , 如果有时间进入某个洞口,就建一条边 , 如果有时间挖洞,就再弄一个洞,连上一条边。
然后二分时间,判断最大流是否大于K就行了。
这个模型其实也可以套到二分匹配 , 不一定要用最大流。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std ;
#define MAXN 1005
#define MAXM 100005
#define INF 0x7fffffff
int mp[105][105] ;
struct Graph {
int Index ;
int head[MAXN] ;
struct Edge {
int to , flow , nex ;
Edge() {}
Edge ( int To , int Flow , int Nex ) : to ( To ), flow ( Flow ), nex ( Nex ) {};
} edge[MAXM * 2] ;
void init() {
Index = 0 ;
memset ( head, -1, sizeof ( head ) );
}
void add ( int from , int to , int f ) {
edge[Index] = Edge ( to , f , head[from] ) ;
head[from] = Index ++ ;
}
} gra;
// 预处理
int n , m , s , t ; // 顶点数 , 边数 , 起点 , 终点
int level[MAXN] ; // 层次图层数
int q[MAXN] ;
int cur[MAXN] ; // 当前弧优化
// 有向边
void insert1 ( int from , int to , int flow ) {
gra.add ( from , to , flow );
gra.add ( to , from , 0 );
}
// 无向边
void insert2 ( int from , int to , int flow ) {
gra.add ( from , to , flow ) ;
gra.add ( to , from , flow ) ;
}
// 构造层次网络
bool bfs() {
memset ( level, 0, sizeof ( level ) );
level[s] = 1;
int top = 0 , rear = 0 ;
q[rear++] = s ;
while ( top != rear ) {
int tmp = q[top++] ;
if ( tmp == t ) return true;
for ( int e = gra.head[tmp] ; ~e ; e = gra.edge[e].nex ) {
int to = gra.edge[e].to , f = gra.edge[e].flow ;
if ( level[to] || !f ) continue ;
level[to] = level[tmp] + 1 ;
q[rear++] = to;
}
}
return false;
}
// 多路增广
int dfs ( int u , int a ) {
if ( u == t || a == 0 ) return a ;
int flow = 0 , f ;
for ( int &e = cur[u] ; ~e ; e = gra.edge[e].nex ) { // 当前弧优化
int to = gra.edge[e].to ;
if ( level[u] + 1 == level[to] && ( f = dfs ( to , min ( a , gra.edge[e].flow ) ) ) > 0 ) {
gra.edge[e].flow -= f ;
gra.edge[e ^ 1].flow += f ;
flow += f ;
if ( ! ( a -= f ) ) break;
}
}
if ( !flow ) level[u] = -1 ; // -1 优化
return flow ;
}
int dinic() {
int ans = 0 ;
while ( bfs() ) {
memcpy ( cur , gra.head , sizeof ( cur ) ) ;
ans += dfs ( s , INF ) ;
}
return ans ;
}
int N , M , K , C ;
void buildGraph( int limit ) {
gra.init() ;
s = 0 ; t = N + 2 * M + 1 ;
for( int i = 1 ; i <= N ; i ++ ) {
insert1( s , i , 1 ) ;
}
for( int i = N + 1 ; i <= N + 2 * M ; i ++ ) {
insert1( i , t , 1 ) ;
}
for( int i = 1 ; i <= N ; i ++ ) {
for( int j = 1 ; j <= M ; j ++ ) {
if( mp[i][j] <= limit ) {
insert1( i , N + j , 1 ) ;
}
if( mp[i][j] + C <= limit ) {
insert1( i , N + j + M , 1 ) ;
}
}
}
}
int search( int l ,int r ) {
int mid ;
while( l < r ) {
mid = ( l + r ) >> 1 ;
buildGraph( mid ) ;
if( dinic() >= K ) {
r = mid ;
}else{
l = mid + 1 ;
}
}
return l ;
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
scanf( "%d%d%d%d" , &N , &M , &K , &C ) ;
int MM = 0 ;
for( int i = 1 ; i <= N ; i ++ ) {
for( int j = 1 ; j <= M ; j ++ ) {
scanf( "%d" , &mp[i][j] ) ;
MM = max( MM , mp[i][j] ) ;
}
}
printf( "%d\n" , search( 1 , MM + C ) ) ;
}
return 0 ;
}
B SPOJ 13042. Gandalf vs the Balrog
题意 : n 个人物 , 分别标号为 1 ~ n , 正常情况下, 标号大的可以打败标号小的 。 但是有m个不正常的关系 ( u, v)表示u可以打败v , u < v 。有两个人 , 如果第一个人选了一个人物 , 第二个人怎么选都不能打败他,那么输出 2 和 所选人物 。如果第一个人选了一个人物 ,第二个人选一个人物可以打败他 , 但第一个人还能选择另一个人打败他,那么输出1。前面两种情况都不满足,就输出0
思路 : 首先我们看什么情况是第一种情况 , 对于一个数,如果能打败所有比他的大的( 或者他本来就是最大的 ) , 并且没有比他小的能打败他 , 那么他就满足第一种情况 ,我们选出其中最大的一个就行 。只要统计一下他能打败多少个大于他的,和多少个比他小的能打败他就行了。
如果找不到就直接输出 "1" 就行了 , 因为第二种情况在第一种情况未满足的条件下是一定满足的 。
这个随便推一下就出来 ,
比如第一个人选了 A ,因为A一定不满足第一种情况,说明有人能打败他 , 那么假定是B
那么 对于 B , 他显然也不满足第一种情况 , 那么也说明有人能打败他 , 并且这个人不是A , 那么就一定存在第三个人C , 那么就满足第二种情况了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int out[1000005] ;
int in[1000005] ;
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
int n , m ;
scanf( "%d%d" , &n , &m ) ;
memset( in , 0 , sizeof(in) ) ;
memset( out , 0 , sizeof(out) ) ;
while( m -- ) {
int a, b ;
scanf( "%d%d" , &a , &b ) ;
out[a] ++ ;
in[b] ++ ;
}
int ans = -1 , val = 2 ;
for( int i = n ; i >= 1 ; i -- ) {
if( in[i] == 0 && out[i] == n - i ) {
ans = i ;
break;
}
}
if( ans != -1 ) {
printf( "%d %d\n" , val , ans ) ;
}else{
puts("1") ;
}
}
return 0 ;
}
D SPOJ 13043. The Mirror of Galadriel
题意 : 判断一个串反转之后能不能在这个串中找到原串的所有子串
思路 : 因为原串本身也算原串的子串 , 所以判断字符串是不是回文串就好了
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
char str[1005] ;
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
scanf( "%s" , str) ;
int len = strlen( str ) ;
int i = 0 , j = len - 1 ;
bool flag = true ;
while( i < j ) {
if( str[i] != str[j] ) {
flag = false ;
break;
}
i ++ ; j -- ;
}
puts( flag?"YES":"NO") ;
}
return 0 ;
}
E SPOJ 13044. Dyslexic Gollum(状态压缩DP)
题意 : 问长度为n的01串,有多少是不含有 长度大于等于 K ( K <= 10 ) 的回文串的
思路 : 首先应该注意到一点 , 就是如果包含长度大于等于 K 的回文串 ,那么一定是包括长度为K的回文串 , 或者长度为K + 1 的回文串。
那么问题就转换为 , 多少字符串不包含长度为K和K+1的回文串
n < K , 那么答案是 2^n
n == K , 直接数下就好了 , 又不大
n > K , 我们设 dp[len][mask] 为 字符串的前 len 个字符 , 最后 K + 1 个字符串的状态是 mask 有多少字符串是不包含长度为K或者K+1的回文串的
那么转移也很简单 , 只要枚举下一个位是0还是1,然后判断下mask的后K为或者K+1为是不是回文串就成
另外注意下赋初值的时候,我们是从 长度为K+1的时候最为递推起点的 , 所以我们不仅要判断后面K+1位和K位是不是回文串,前K位也需要判断下。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MOD 1000000007
typedef long long LL ;
//bool state[12][(1<<12)] ;
bool mask[12][(1<<12)] ;
LL dp[405][(1<<12)] ;
void init(){
for( int i = 1 ; i <= 11 ; i ++ ) {
int size = ( 1<<i ) ;
for( int j = 0 ; j < size ; j ++ ) {
int s = 0 , t = i - 1 ;
mask[i][j] = true ;
while( s < t ) {
if( ( ( j >> s ) & 1 ) != ( ( j >> t ) & 1 ) ) {
mask[i][j] = false ;
break;
}
s ++ ; t -- ;
}
}
}
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
init() ;
while( cas -- ) {
int n , k ;
scanf( "%d%d" , &n , &k ) ;
if( n < k ) {
printf( "%d\n" , (1<<n) ) ;
continue ;
}
if( n == k ) {
int cnt = 0 ;
for( int i = 0 ; i < ( 1 << n ) ; i ++ ) {
cnt += (mask[n][i]^1) ;
}
printf( "%d\n" , cnt ) ;
continue ;
}
memset( dp , 0 , sizeof(dp) ) ;
int size = ( 1<<(k+1) ) ;
int All = size - 1 ;
int all = ( 1 << k ) - 1 ;
for( int i = 0 ; i < size ; i ++ ) {
if( mask[k+1][i] || mask[k][i&all] || mask[k][i>>1] ) continue ;
dp[k+1][i] = 1 ;
}
for( int i = k + 1 ; i < n ; i ++ ) {
for( int j = 0 ; j < size ; j ++ ) {
if( dp[i][j] ) {
int nexmask = ( (j<<1) & All ) ;
if( mask[k+1][nexmask] == false && mask[k][nexmask&all] == false ) {
dp[i+1][nexmask] += dp[i][j] ;
dp[i+1][nexmask] %= MOD ;
}
nexmask |= 1 ;
if( mask[k+1][nexmask] == false && mask[k][nexmask&all] == false ) {
dp[i+1][nexmask] += dp[i][j] ;
dp[i+1][nexmask] %= MOD ;
}
}
}
}
LL ans = 0 ;
for( int i = 0 ; i < size ; i ++ ) {
ans += dp[n][i] ;
ans %= MOD ;
}
printf( "%lld\n" , ans ) ;
}
return 0 ;
}
G SPOJ 13046. The Glittering Caves of Aglarond(贪心)
题意 : n * m 列的矩阵 , '.' 表示灯灭的 , '*'表示灯亮的 , 必须做K次操作 , 问灯亮的最多可以有多少
思路 : 简单的贪心 , 先统计出每场中灯亮着的个数 , 明显一个想法就是一直改变的是亮灯数最少的 。数据量很小,有了这个思路之后就随便怎么搞了 ...
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std ;
int n , m , k ;
char mp[55][55] ;
int cnt[55] ;
bool cmp( int a , int b ) {
return a > b ;
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
scanf( "%d%d%d" , &n , &m , &k ) ;
memset( cnt , 0 , sizeof(cnt) ) ;
int sum = 0 ;
for( int i = 1 ; i <= n ; i ++ ) {
scanf( "%s" , mp[i] + 1 ) ;
for( int j = 1 ;j <= m; j ++ ) {
if( mp[i][j] == '*' ) {
cnt[i] ++ ;
sum ++ ;
}
}
}
sort( cnt + 1 , cnt + 1 + n , cmp ) ;
for( int i = n ; i >= 1 && k ; i -- ) {
if( m - cnt[i] > cnt[i] ) {
sum = sum - cnt[i] + ( m - cnt[i] ) ;
cnt[i] = m - cnt[i] ;
k -- ;
}else{
break;
}
}
k %= 2 ;
if( k == 1 ) {
sort( cnt + 1 , cnt + 1 + n ) ;
sum = sum - cnt[1] + ( m - cnt[1] ) ;
}
printf( "%d\n" , sum ) ;
}
return 0 ;
}
I SPOJ 13047. Saruman of Many Colours
题意: 给你n个格子,原来都是没有颜色,给你现在的颜色,不同的字母表示不同颜色,你一次可以刷连续k个格子,一次可以刷任意颜色( 当然一次刷的颜色是相同的 ) , 问最少刷几次才能得到给你颜色。
思路 : 首先最后一笔的长度必然是没有其他颜色可以覆盖的,所以我们首先要找到一个至少连续出现k个相同颜色的区间,如果找不到,那么肯定是-1
不是-1的情况下,涂的顺序肯定是满足拓扑序的,所以我们只要统计必须要涂的次数即可。
那么对于第一个格子,我们是必须要涂的,我们从第一个格子往右延伸,假设有A个连续相同颜色的,我们就至少要涂 ceil( A / k ) 次 ,涂完之后,我们要考虑下面怎么涂。其实下面的问题就是以第A+1个格子开始的相似问题,用类似的方式解决就行了。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
char str[20005] ;
int main(){
int cas ;
scanf( "%d" , &cas ) ;
int n , k ;
while( cas -- ) {
int times = 0 ;
scanf( "%d%d" , &n , &k ) ;
scanf( "%s" , str ) ;
int lianxu = 1 ;
bool flag = false ;
for( int i = 1 ; i < n ; i ++ ) {
if( str[i] == str[i-1] ) {
lianxu ++ ;
}else{
if( lianxu >= k ) {
flag = true ;
break;
}
lianxu = 1 ;
}
}
if( lianxu >= k ) flag = true ;
if( flag == false ) {
puts( "-1" ) ;
continue ;
}
int ans = 0 ;
for( int i = 0 ; i < n ; i ++ ) {
if( i == 0 || str[i]!=str[i-1] ) {
int cnt = 0 ;
for( int j = i ; j < n ; j ++ ) {
if( str[j] == str[i] ) cnt ++ ;
else break;
}
ans += ceil( 1.0 * cnt / k ) ;
}
}
printf( "%d\n" , ans ) ;
}
return 0 ;
}
K SPOJ 13049. The Loyalty of the Orcs
题意 : 有 n 个人 , 这n个人有上下级关系 , 关系为一棵树 ,有m个挂了。 我们要按照某种固定的顺序来看每个人的死掉没有 , 本来每个人都要看的 , 不过有人提出 , 如果已经发现了这个人的上司( 不一定是顶头上司 ) 有人挂了 , 那么这个人就不用看了 。1号一定是boss。顺序是随机的,问期望能少看多少个人。
思路 : 因为每个顺序出现的概率是相等的 , 所以期望就是平均情况 。那么 ans = ( 减少的总数 ) / n!
那么我们看看减少的总数怎么算 , 减少的总数 = 1号被忽略总次数 + 2号被忽略的总次数 + ... + n号被忽略的总次数
要求出某一个人被忽略的总次数 , 我们先要知道这个人挂了几个上司 , 这个只要dfs一遍就可以求出
假设有第i个人挂了M个上司,第 i 个人被忽略的总次数 = n! - 第i个人不被忽略的总次数
什么情况不被忽略 , 那就是这M个上次都排在他的后面 , 那么这里有 M! 个 , 然后剩下来的人插空法插进去就行。
即现在有 M + 1 个人, 有 M + 2 个空 , 那么第 M + 2 个人就有M+2个选择,以此类推,第M+3个人有M+3个选择 ...
所以 第i个人不被忽略的总次数是 M! * (M+2) * (M+3) .. *( n ) = n ! / (M+1)
第i个人被忽略的总次数是 ( n!-n!/(M+1) ) ,除掉分母就是 1 - 1/( M +1 )
所以最后的答案就是 n - Σ( 1 / ( Mi + 1 ) )
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 100005
struct Tree{
struct Edge{
int to , nex ;
Edge(){}
Edge( int _to , int _nex ){
to = _to ;
nex = _nex ;
}
}edge[MAXN*2];
int head[MAXN] ;
int Index ;
void init(){
memset( head , -1 , sizeof(head) ) ;
Index = 0 ;
}
void add( int from , int to ) {
edge[Index] = Edge( to , head[from] ) ;
head[from] = Index ++ ;
}
}tree ;
double sum ;
int gua[MAXN] ;
void dfs( int u , int f , int die ) {
sum += 1.0 / ( die + 1.0 ) ;
for( int i = tree.head[u] ; ~i ; i = tree.edge[i].nex ) {
int ch = tree.edge[i].to ;
if( ch == f ) continue ;
dfs( ch , u , die + gua[u] ) ;
}
}
int main(){
int cas ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
tree.init() ;
memset( gua , 0 , sizeof(gua) ) ;
int n ;
scanf( "%d" , &n ) ;
for( int i = 1 ; i < n ; i ++ ) {
int u , v ;
scanf( "%d%d" , &u , &v ) ;
tree.add( u , v ) ;
tree.add( v , u ) ;
}
int m ;
scanf( "%d" , &m ) ;
sum = 0 ;
for( int i = 1 ; i <= m ; i ++ ) {
int u ;
scanf( "%d" , &u ) ;
gua[u] = 1 ;
}
dfs( 1 , -1 , 0 ) ;
printf( "%.10lf\n" , n - sum ) ;
}
return 0 ;
}