纪中国庆10.4做题小结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ha_ing/article/details/102093893

T1:教主的花园

Description
  教主有着一个环形的花园,他想在花园周围均匀地种上n棵树,但是教主花园的土壤很特别,每个位置适合种的树都不一样,一些树可能会因为不适合这个位置的土壤而损失观赏价值。
  教主最喜欢3种树,这3种树的高度分别为10,20,30。教主希望这一圈树种得有层次感,所以任何一个位置的树要比它相邻的两棵树的高度都高或者都低,并且在此条件下,教主想要你设计出一套方案,使得观赏价值之和最高。

Input
  输入的第1行为一个正整数n,表示需要种的树的棵树。
  接下来n行,每行3个不超过10000的正整数ai,bi,ci,按顺时针顺序表示了第i个位置种高度为10,20,30的树能获得的观赏价值。
  第i个位置的树与第i+1个位置的树相邻,特别地,第1个位置的树与第n个位置的树相邻。

Output
  输出仅包括一个正整数,为最大的观赏价值和。

Sample Input

4
1 3 2
3 1 2
3 1 2
3 1 2

Sample Output

11

Hint
【样例说明】
  第1~n个位置分别种上高度为20,10,30,10的树,价值最高。
【数据规模】
  对于20%的数据,有n≤10;
  对于40%的数据,有n≤100;
  对于60%的数据,有n≤1000;
  对于100%的数据,有4≤n≤100000,并保证n一定为偶数。

简要思路:这题其实是一个要考虑后效性的环形 d p dp ,直观上来看,本题样例的答案是这样得出的:图1

我用 d p [ i ] [ j ] [ 0 / 1 ] [ k ] dp[i][j][0/1][k] 来表示该状态下的最大价值,其中 i i 表示种到第 i i 棵树, j j 表示树的高度为10,20,30(用1,2,3表示),0,1分别表示当前树的高度比上一棵树小或大,而 k k 记录第一棵树的高度,用于处理环。我这种方法要手动处理前两棵树的每一种情况。 d p dp 具体的转移不难,但要分类讨论。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 100005
using namespace std;
int n;
int dp[N][4][2][4];
int val[N][4];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
int main () {
	read(n);
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= 3 ; ++j ) {
			read(val[i][j]);
		}
	}
	dp[2][1][0][2] = val[1][2] + val[2][1];
	dp[2][1][0][3] = val[1][3] + val[2][1];
	dp[2][3][1][1] = val[1][1] + val[2][3];
	dp[2][3][1][2] = val[1][2] + val[2][3];
	dp[2][2][1][1] = val[1][1] + val[2][2];
	dp[2][2][0][3] = val[1][3] + val[2][2];
	for ( int i = 2 ; i <= n - 1 ; ++i ) {
		if ( i == n - 1 ) {//最后一棵树的高度受到第一棵树高度的限制,要特判。
			for ( int j = 1 ; j <= 3 ; ++j ) {
				switch(j) {
					case 1 : {
						for ( int k = 1 ; k <= 1 ; ++k ) {
							dp[i + 1][2][1][k] = max( dp[i + 1][2][1][k] , dp[i][j][0][k] + val[i + 1][2] );
						}
						for ( int k = 1 ; k <= 2 ; ++k ) {
							dp[i + 1][3][1][k] = max( dp[i + 1][3][1][k] , dp[i][j][0][k] + val[i + 1][3] );
						}
						break;
					}
					case 3 : {
						for ( int k = 2 ; k <= 3 ; ++k ) {
							dp[i + 1][1][0][k] = max( dp[i + 1][1][0][k] , dp[i][j][1][k] + val[i + 1][1] );
						}
						for ( int k = 3 ; k <= 3 ; ++k ) {
							dp[i + 1][2][0][k] = max( dp[i + 1][2][0][k] , dp[i][j][1][k] + val[i + 1][2] );
						}
						break;
					}
					case 2 : {
						for ( int k = 2 ; k <= 3 ; ++k ) {
							dp[i + 1][1][0][k] = max( dp[i + 1][1][0][k] , dp[i][j][1][k] + val[i + 1][1] );
						}
						for ( int k = 1 ; k <= 2 ; ++k ) {
							dp[i + 1][3][1][k] = max( dp[i + 1][3][1][k] , dp[i][j][0][k] + val[i + 1][3] );
						}
						break;
					}
				}
			}
		} else {
			for ( int j = 1 ; j <= 3 ; ++j ) {
				switch(j) {
					case 1 : {
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][2][1][k] = max( dp[i + 1][2][1][k] , dp[i][j][0][k] + val[i + 1][2] );
						}
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][3][1][k] = max( dp[i + 1][3][1][k] , dp[i][j][0][k] + val[i + 1][3] );
						}
						break;
					}
					case 3 : {
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][1][0][k] = max( dp[i + 1][1][0][k] , dp[i][j][1][k] + val[i + 1][1] );
						}
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][2][0][k] = max( dp[i + 1][2][0][k] , dp[i][j][1][k] + val[i + 1][2] );
						}
						break;
					}
					case 2 : {
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][1][0][k] = max( dp[i + 1][1][0][k] , dp[i][j][1][k] + val[i + 1][1] );
						}
						for ( int k = 1 ; k <= 3 ; ++k ) {
							dp[i + 1][3][1][k] = max( dp[i + 1][3][1][k] , dp[i][j][0][k] + val[i + 1][3] );
						}
						break;
					}
				} 
			}
		}
	}
	int ans = 0;
	for ( int i = 1 ; i <= 3 ; ++i ) {
		for ( int j = 0 ; j <= 1 ; ++j ) {
			for ( int k = 1 ; k <= 3 ; ++k ) {
				ans = max( dp[n][i][j][k] , ans );
			}
		}
	}
	printf("%d",ans);
	return 0;
}
/*
6
1 3 2
3 1 2
3 1 2
3 1 2
1 1 1
5 1 5
*/

T2:教主的游乐场

Description
  Orz教主的成员为教主建了一个游乐场,在教主的规划下,游乐场有一排n个弹性无敌的跳跃装置,它们都朝着一个方向,对着一个巨大的湖,当人踩上去装置可以带你去这个方向无限远的地方,享受飞行的乐趣。但是等这批装置投入使用时,却发现来玩的人们更喜欢在这些装置上跳来跳去,并且由于这些装置弹性的优势,不但它们能让人向所对的方向能跳很远,也都能向相反方向跳一定的距离。
  于是教主想出了个游戏,这n个装置按朝向相反的方向顺序以1…n编号。第i个装置可以跳到1…i-1个装置,且每个装置有一个不一定相同的反方向弹性a[i],代表第i个装置还可以跳到第i+1…i+a[i]个装置。教主指定一个起始的装置,问从这个装置开始,最少需要连续踩几次装置(起始的装置也算在内),可以跳到第n个装置的后方,即若第k个装置有k+a[i]>n,那么从第k个装置就可以跳到第n个装置的后方。
  (PS:你可以认为有n+1个装置,即需要求多少次能条到第n+1个装置)

扫描二维码关注公众号,回复: 7573510 查看本文章

Input
  输入的第1行包含两个正整数n,m,为装置的数目以及询问的次数。
  第2行包含n个正整数,第i个正整数为a[i],即第i个装置向反方向最大跳跃的长度。
  第3行包含了m个正整数,为询问从哪一个装置开始,最少要几次跳到第n个的后方。
  数字之间用空格隔开。

Output
  输出包含1行,这一行有m个正整数,对于每一个询问,输出最少需要踩的装置数,数字之间用空格隔开。
  行末换行且没有多余的空格。

Sample Input

5 5
2 4 1 1 1
1 2 3 4 5

Sample Output

2 1 2 2 1

Hint
【样例说明】
  若从第1个装置开始则跳到第2个装置,接着就可以跳到第n个装置的后方。
  若从第3个装置开始则同样跳到第2个装置。
  若从第4个装置开始可以跳到第2个装置或最后一个装置,接着跳出第n个装置,答案同样为2。
【数据规模】
  对于20%的数据,有n≤10;
  对于40%的数据,有n≤100,m≤10;
  对于60%的数据,有n≤1000,a[i]≤1000,m≤500;
  对于100%的数据,有n≤100000,a[i]≤n,m≤50000。

简要思路:这题有个贪心做法,就是从前往后,找到第一个能跳到最外边(或大于期望最大值)的点,那么这个点右边的点要么自己也能跳到目标位置,要么跳到这个点上再跳到目标位置。这种做法可不断缩小问题规模,在随机数据下表现优秀,但现在在JZOJ上会被卡,只有90分了。

//90分
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 100005
using namespace std;
int n , m;
int a[N] , dis[N];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
int main () {
	//freopen( "data8.in" , "r" , stdin );
	//freopen( "8.out" , "w" , stdout );
	read(n);
	read(m);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(a[i]);
	}
	int end = n + 1;
	dis[end] = 0;
	int tem;
	while ( end > 1 ) {
		for ( int i = 1 ; i <= end - 1 ; ++i ) {
			if ( a[i] + i >= end ) {
				tem = i;
				dis[tem] = dis[end] + 1;
				break;
			}
		}
		for ( int i = tem + 1 ; i <= end - 1 ; ++i ) {
			if ( a[i] + i >= end ) {
				dis[i] = dis[end] + 1;
			} else {
				dis[i] = dis[tem] + 1;
			}
		}
		end = tem;
	}
	bool fir = true;
	int x;
	for ( int i = 1 ; i <= m ; ++i ) {
		read(x);
		if ( fir ) {
			fir = false;
		} else {
			printf(" ");
		}
		printf("%d",dis[x]);
	}
	printf("\n");
	return 0;
}

这题其实可以转化为一个区间覆盖问题,一开始只考虑一个点向右跳的情况,从右到左处理点,用线段树维护每个点向右跳的最小步数,每当处理一个新点时,如果它不能直接跳到外界,就查询 [ x , x + a [ x ] ] [x,x+a[x]] 的最小值,它的最小值为查到的最小值加一。最后考虑可以向左跳的情况,从左到右存贮所查到的点的最小值,按照循环顺序更新右边点的答案即可。

//100分
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 100005
using namespace std;
int n , m;
int a[N] , front[N] , dis[N];
struct segmenttree{
	int val[4 * N];
	inline void insert( int pos , int l , int r , int p , int v ) {
		if ( l == r ) {
			val[pos] = v;
			return;
		}
		int mid = ( l + r ) >> 1;
		if ( p <= mid ) {
			insert( pos << 1 , l , mid , p , v );
		} else {
			insert( ( pos << 1 ) + 1 , mid + 1 , r , p , v );
		}
		val[pos] = min( val[pos << 1] , val[( pos << 1 ) + 1] );
		return;
	}
	inline int query( int pos , int l , int r , int x , int y ) {
		if ( l >= x && r <= y ) {
			return val[pos];
		}
		int mid = ( l + r ) >> 1;
		int min1 , min2;
		min1 = min2 = 0x3f3f3f3f;
		if ( x <= mid ) {
			min1 = query( pos << 1 , l , mid , x , y );
		}
		if ( y >= mid + 1 ) {
			min2 = query( ( pos << 1 ) + 1 , mid + 1 , r , x , y );
		}
		return min( min1 , min2 );
	}
}tree;
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
int main () {
	read(n);
	read(m);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(a[i]);
	}
	memset( tree.val , 0x3f , sizeof( tree.val ) );
	if ( a[n] >= 1 ) { //这是废话 
		front[n] = 1;
	}
	tree.insert( 1 , 1 , n , n , front[n] );
	for ( int i = n - 1 ; i >= 1 ; --i ) {
		if ( a[i] + i > n ) {
			front[i] = 1;
		} else {
			front[i] = tree.query( 1 , 1 , n , i + 1 , i + a[i] ) + 1;
		}
		tree.insert( 1 , 1 , n , i , front[i] );
	}
	int minf = 0x3f3f3f3f;
	for ( int i = 1 ; i <= n ; ++i ) {
		dis[i] = min( front[i] , minf + 1 );
		minf = min( minf , dis[i] ); 
	}
	bool fir = true;
	int x;
	for ( int i = 1 ; i <= m ; ++i ) {
		read(x);
		if ( fir ) {
			fir = false;
		} else {
			printf(" ");
		}
		printf("%d",dis[x]);
	}
	printf("\n");
	return 0;
}

T3:教主的集合序列

Description
  定义集合S1为1到n之间所有正整数组成的集合,即S1={1,2,3…n}。当k>1时,Sk为集合Sk-1中任意两个不相同数之和的集合。
  例如,当n=3时:
  S1={1,2,3}
  S2={3,4,5}
  S3={7,8,9}
  S4={15,16,17}
  ……
  现将每个集合中所有元素取出,集合Si的数放在集合Si+1的数的前面,同一个集合数从小到大排序,这样得到一个序列L。例如,当n=3时,L=1,2,3,3,4,5,7,8,9,15,16,17……
  那么,现对于给定的n和k,求L中第k个数。

Input
  输入包含1行,为2个正整数n和k,两个整数用空格隔开。

Output
  输出包含1行,为一个正整数,即序列L中第k个数。若这个数不存在,则输出-1。

Sample Input

4 6

Sample Output

4

Hint
【样例说明】
  当n=4时,序列L=1,2,3,4,3,4,5,6,7,7……
  所以序列中第6个数为4。
【数据规模】
  对于20%的数据,有k≤20;
  对于40%的数据,有k≤10000,n≤5;
  对于50%的数据,满足答案不超过2^31-1;
  对于60%的数据,有k≤2^30-1;
  对于80%的数据,有k≤10^100;
  对于100%的数据,有k≤10^1000,1<n≤1000,对于n≤3有k≤90000。

简要思路:当n = 1或2时,集合的总数是有限的,总共为 1 {1} 以及 1 , 2 , 3 {1,2,3} ,询问的范围超了就可以直接打出"-1"。根据题意,不难发现每个集合中的数都是连续的,因此集合可用区间表示。当n = 3时,观察可得集合的元素总数不变,对于第k个集合,第一个数为 2 k 1 2^k-1 (自己证),第二三个为 2 k 2^k 2 k + 1 2^k+1
n 4 n \ge 4 时,第 k k 个集合区间长度为 2 k 1 ( n 3 ) + 3 2^{k-1}*(n-3)+3 ,表示为 [ 2 k 1 , 2 k 1 ( n 1 ) + 1 ] [2^k-1,2^{k - 1}*(n - 1) + 1] (用到等比数列求和公式推导的)。实现要靠高精度(本题难点所在)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define mod 10000000
#define N 1010
#define M 8100
#define ll long long
using namespace std;
ll n , m;
ll ans[3][4];
ll num[M];
char s[N];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline void write( ll tem[] ) {
	printf("%lld",tem[tem[0]]);
	for ( int i = tem[0] - 1 ; i >= 1 ; --i ) {
		printf("%07lld",tem[i]);
	}
	return;
}
inline int cmp( ll a[] , ll b[] ) { // 2 : a = b  0 : a < b 1 : a > b
	if ( a[0] != b[0] ) {
		return (a[0] > b[0]);
	}
	for ( int i = a[0] ; i >= 1 ; --i ) {
		if ( a[i] != b[i] ) {
			return (a[i] > b[i]);
		}
	}
	return 2;
}
inline void mul( ll a[] , ll b[] , ll c[] ) {
	//memset( c , 0 , sizeof(c) );
	ll t[M] = {0};
	t[0] = a[0] + b[0];
	for ( int i = 1 ; i <= a[0] ; ++i ) {
		for ( int j = 1 ; j <= b[0] ; ++j ) {
			t[i + j - 1] += a[i] * b[j];
		}
	}
	for ( int i = 1 ; i <= t[0] ; ++i ) {
		t[i + 1] += t[i] / mod;
		t[i] %= mod;
	}
	while ( !t[t[0]] && t[0] > 1 ) {
		--t[0];
	}
	memcpy( c , t , sizeof(t) );
	return;
}
inline void qser( ll tem[] , ll x ) {
	//memset( tem , 0 , sizeof(tem) );
	ll t[M] = {0};
	t[0] = 1;
	t[1] = 2;
	tem[0] = tem[1] = 1;
	while ( x ) {
		if ( x & 1 ) {
			mul( tem , t , tem );
		}
		mul( t , t , t );
		x >>= 1;
	}
	return;
}
inline void summ( ll a[] , ll b[] , ll c[] ) {
	//memset( c , 0 , sizeof(c) );
	ll t[M] = {0};
	t[0] = max( a[0] , b[0] );
	for ( int i = 1 ; i <= t[0] ; ++i ) {
		t[i] = a[i] + b[i];
	}
	for ( int i = 1 ; i <= t[0] ; ++i ) {
		t[i + 1] += t[i] / mod;
		t[i] %= mod;
	}
	while ( t[t[0] + 1] ) {
		++t[0];
	}
	memcpy( c , t , sizeof(t) );
	return;
}
inline void minu( ll a[] , ll b[] , ll c[] ) {
	//memset( c , 0 , sizeof(c) );
	ll t[M] = {0};
	t[0] = a[0];
	for ( int i = 1 ; i <= t[0] ; ++i ) {
		t[i] = a[i] - b[i];
	}
	for ( int i = 1 ; i <= t[0] - 1 ; ++i ) {
		if ( t[i] < 0 ) {
			--t[i + 1];
			t[i] += mod;
		}
	}
	while ( !t[t[0]] && t[0] > 1 ) {
		--t[0];
	}
	memcpy( c , t , sizeof(t) );
	return;
}
inline void trans( char ss[] , ll a[] ) {
	int l = strlen(ss);
	a[0] = 1;
	int tem;
	for ( tem = l ; tem > 6 ; tem -= 7 ) {
		for ( int j = 0 ; j <= 6 ; ++j ) {
			a[a[0]] = a[a[0]] * 10 + ( ss[tem - 7 + j] - '0' );
		}
		++a[0];
	}
	for ( int j = 0 ; j < tem ; ++j ) {
		a[a[0]] = a[a[0]] * 10 + ( ss[j] - '0' );
	}
	return;
}
inline void cal( ll a[] , ll i , ll b[] ) {
	qser( a , i - 1 );
	b[1] = n - 3;
	mul( a , b , a );
	b[1] = i * 3;
	summ( a , b , a );
	b[1] = n;
	minu( a , b , a );
	return;
}
inline void work() {
	ll t[M] = {0};
	ll tem[M] = {0};
	tem[0] = 1;
	int l = 1;
	int r = 5000;
	while ( l < r ) {
		int mid = ( l + r + 1 ) >> 1;
		cal( t , mid , tem );
		int te = cmp( num , t );
		if ( te == 2 ) {
			qser( t , mid - 2 );
			tem[1] = n - 1;
			mul( t , tem , t );
			tem[1] = 1;
			summ( t , tem , t );
			write( t );
			return;
		}
		if ( te ) {
			l = mid;
		} else {
			r = mid - 1;
		}
	}
	cal( t , r , tem );
	minu( num , t , num );
	qser( t , r );
	tem[1] = 2;
	minu( t , tem , t );
	summ( num , t , t );
	write(t);
}
int main () {
	read(n);
	if ( n < 3 ) {
		ans[1][1] = 1;
		ans[2][1] = 1;
		ans[2][2] = 2;
		ans[2][3] = 3;
		read(m);
		if ( m <= 3 && ans[n][m] > 0 ) {
			printf("%lld",m);
		} else {
			printf("-1");
		}
		return 0;
	} else if ( n == 3 ) {
		read(m);
		qser( num , ( m + 2 ) / 3 );
		if ( m % 3 == 1 ) {
			--num[1];
		}
		if ( m % 3 == 0 ) {
			++num[1];
		}
		write(num);
		return 0;
	} else {
		memset( num , 0 , sizeof(num) );
		scanf("%s",s);
		trans( s , num );
		work();
	}
	return 0;
}

T4 :教主的难题

Description
  一个数x可以按以下规则生成数字:
  1、将任意两位交换,若交换的数字为a和b,生成的代价为((a and b)+(a xor b))*2。
  例如134可以生成431,因为431可以从134的个位(4)与百位(1)交换后得到,代价为((1 and 4)+(1 xor 4))*2=10。
  2、将其中一位数删除,但是删除的数要满足大于等于它左边的数,且小等于它右边的数,并且定义最高位左边的数为个位,个位右边的数为最高位。若删除的数字为a,它左边的数为b,它右边的数为c,则生成的代价为a+(b and c)+(b xor c)。
  例如212,可以删除个位的数得到21,但是因为2>1,所以1是不能被删除的。特别地,若x为两位数,它能删除当且仅当x的两位数相同,若x为一位数,它是不能被删除的。
  3、在两位数字之间,也可以是数字的前面或后面加入一位数,同样地,加入的数要满足大等于它左边的数,且小等于它右边的数,并且定义最高位左边的数为个位,个位右边的数为最高位。若加入的数字为a,它左边的数为b,它右边的数为c,则生成的代价为a+(b and c)+(b xor c)。
  例如241,它可以在2与4之间加入一个3,生成2341,也可以在数的末尾加入一个1或者2,当然还有其它可以生成的数,但是不能在4和1之间加入数字。
  你的任务是,S一开始为n个不同的给定数组成的集合,每次可以从S中取出一个数生成一个满足生成规则的数加入S中,并且取出的数仍然存在于S中。生成的数的位数不能大于S初始集合最大的数的位数。问在S元素最多的情况下,总代价最小是多少。

Input
  输入的第1行为一个正整数n,为集合S初始元素个数。
  第2行包含n个正整数a1,a2, …, an,数之间空格隔开,为S中初始元素。

Output
  输出包括一个正整数,为最小代价。

Sample Input

2
111 22

Sample Output

12

Hint
【样例说明】
  111删除1得到11,代价为2,11删除1得到1,代价为2,同样22删除和加入一个2得到2,222,代价均为4,总代价2+2+4+4=12。111无法生成1111因为111为一个3位数,而1111为一个4位数。
  利用交换/添加规则无法让集合元素更多,所以最小代价为12。

【数据规模】
  对于20%的数据,有ai<100;
  对于40%的数据,有ai<1000;
  对于50%的数据,有n=1;
  对于60%的数据,有ai<10000;
  对于100%的数据,有ai<100000,n≤5,保证的任何一位不包含0。

简要思路:这题通过率最低,但最好讲,对于一个数 x x ,它通过任意一种方式转化为 y y ,可以表示为从 x x y y 连一条价值为转化价值的边,由于所有可得到的点是有限的,再把原有的几个点连上边权为零的边,我们不难得出一个图,由于图比较稀疏,只要用 k r u s k a l kruskal 跑个最小生成树即可。建图的具体操作见代码吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n , bcnt , ncnt , maxn;
int num[6];
int vis[100005];
int f[100005];
struct node{
	int x;
	int y;
	int val;
}str[500005];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline void add( int x , int y , int val ) {
	str[++bcnt].x = x;
	str[bcnt].y = y;
	str[bcnt].val = val;
	return;
}
inline int find( int x ) {
	if ( x == f[x] ) {
		return x;
	} else {
		f[x] = find( f[x] );
		return f[x];
	}
}
inline bool cmp( node a , node b ) {
	return a.val < b.val;
}
inline void dfs( int cur ) {
	int now[6] , tem[6];
	int l = 0;
	int t = cur;
	while ( t ) {
		now[++l] = t % 10;
		t /= 10;
	}
	for ( int i = 1 ; i < l ; ++i ) {
		for ( int j = i + 1 ; j <= l ; ++j ) {
			for ( int k = 1 ; k <= l ; ++k ) {
				tem[k] = now[k];
			}
			tem[i] = now[j];
			tem[j] = now[i];
			t = 0;
			for ( int k = l ; k >= 1 ; --k ) {
				t = t * 10 + tem[k];
			}
			int val = 2 * ( ( tem[i] & tem[j] ) + ( tem[i] ^ tem[j] ) );
			add( cur , t , val );
			if ( !vis[t] ) {
				vis[t] = 1;
				ncnt++;
				dfs( t );
			}
		}
	}
	if ( l > 1 ) {
		for ( int i = 1 ; i <= l ; ++i ) {
			int p1 = i + 1;
			int p2 = i - 1;
			if ( p1 == l + 1 ) {
				p1 = 1;
			}
			if ( p2 == 0 ) {
				p2 = l;
			}
			if ( now[i] >= now[p1] && now[i] <= now[p2] ) {
				t = 0;
				for ( int k = l ; k >= 1 ; --k ) {
					if ( k != i ) {
						t = t * 10 + now[k];
					}
				}
				int val = ( now[i] + ( now[p1] & now[p2] ) + ( now[p1] ^ now[p2] ) );
				add( cur , t , val );
				if ( !vis[t] ) {
					vis[t] = 1;
					ncnt++;
					dfs( t );
				}
			}
		}
	}
	if ( cur * 10 >= maxn ) {
		return;
	}
	for ( int i = now[1] ; i <= now[l] ; ++i ) {
		int val = ( i + ( now[1] & now[l] ) + ( now[1] ^ now[l] ) );
		t = cur * 10 + i;
		add( cur , t , val );
		if ( !vis[t] ) {
			vis[t] = 1;
			ncnt++;
			dfs( t );
		}
		t = i;
		for ( int j = l ; j >= 1 ; --j ) {
			t = t * 10 + now[j];
		}
		add( cur , t , val );
		if ( !vis[t] ) {
			vis[t] = 1;
			ncnt++;
			dfs(t);
		}
	}
	for ( int i = 1 ; i < l ; ++i ) {
		int p = i + 1;
		for ( int j = now[p] ; j <= now[i] ; ++j ) {
			int val = ( j + ( now[p] & now[i] ) + ( now[p] ^ now[i] ) );
			t = 0;
			for ( int k = l ; k >= 1 ; --k ) {
				if ( k == i ) {
					t = ( t * 10 + j ) * 10 + now[k];
				} else {
					t = t * 10 + now[k];
				}
			}
			add( cur , t , val );
			if ( !vis[t] ) {
				vis[t] = 1;
				ncnt++;
				dfs( t );
			}
		}
	}
	return;
}
int main () {
	read(n);
	maxn = 1;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(num[i]);
		while ( maxn <= num[i] ) {
			maxn *= 10;
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = i + 1 ; j <= n ; ++j ) {
			add( num[i] , num[j] , 0 );
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		if ( !vis[num[i]] ) {
			vis[num[i]] = 1;
			ncnt++;
			dfs( num[i] );
		}
	}
	sort( str + 1 , str + 1 + bcnt , cmp );
	for ( int i = 1 ; i < maxn ; ++i ) {
		f[i] = i;
	}
	ncnt--;
	int ans = 0;
	for ( int i = 1 ; i <= bcnt ; ++i ) {
		int x = str[i].x;
		int y = str[i].y;
		int fx = find( x );
		int fy = find( y );
		if ( fx != fy ) {
			ans += str[i].val;
			//cout << str[i].val << endl;
			f[fx] = fy;
			ncnt--;
		}
		if ( !ncnt ) {
			break;
		}
	}
	printf("%d",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/ha_ing/article/details/102093893