Regionals 2012 :: Asia - Amritapuri

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 ;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值