纪中8.22一场莫名其妙的比赛

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

前言

你们可能会觉得奇怪,为什么这场比赛会突如其来,原因很简单,今天原本是开讲座的,但由于神犇lkf的讲座过于高深,曲高和寡,很少人听(蒟蒻本色 Q W Q QWQ ),于是cx临时(也可能是计划中的 )决定放了一场比赛。

T1:回文子序列

Description
回文序列是指左右对称的序列。例如1 2 3 2 1是回文序列,但是1 2 3 2 2就不是。我们会给定一个N×M的矩阵,你需要从这个矩阵中找出一个P×P的子矩阵,使得这个子矩阵的每一列和每一行都是回文序列。

Input
第一行有两个正整数N, M。
接下来是n行,代表一个N×M的矩阵,矩阵的每个元素都是值不超过31415926的正整数。

Output
输出符合条件的子矩阵的最大大小P。

Sample Input

5 10
1 2 3 3 2 4 5 6 7 8
1 2 3 3 2 4 5 6 7 8
1 2 3 3 2 4 5 6 7 8
1 2 3 3 2 4 5 6 7 8
1 2 3 9 10 4 5 6 7 8

Sample Output

4

Data Constraint
对于20%数据 1 ≤ N, M ≤ 10
对于所有数据 1 ≤ N, M ≤ 300

简要思路:这题看上去正解好像是马拉车算法,但是,实际上,这题数据过水,暴力可过,如果使用马拉车算法反而有很多细节不好处理。对于这题,我们用 h [ i ] [ j ] h[i][j] 表示从第 i i 行第 j j 列这一位置往右(同一行)最长的回文串的长度,用 l [ i ] [ j ] l[i][j] 表示从第 i i 行第 j j 列这一位置往下(同一列)最长的回文串的长度。最后枚举矩阵左上方的节点,行与列都进行一次检查,取最小值即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 305
using namespace std;
int n , m , ans;
bool flag;
int num[N][N] , h[N][N] , l[N][N];
inline void read( int & res ) {
	res = 0;
	int 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 ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			read(num[i][j]);
		}
	}
	int hh , ll;
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			hh = 0;
			ll = 0;
			for ( int k = j ; k <= m ; ++k ) {
				for ( int p = j ; p <= ( k + j ) / 2 ; ++p ) {
					if ( num[i][p] != num[i][k + j - p] ) {
						break;
					} else if ( p == ( k + j ) / 2 ) {
						hh = k - j + 1;
					}
				}
			}
			for ( int k = i ; k <= n ; ++k ) {
				for ( int p = i ; p <= ( k + i ) / 2 ; ++p ) {
					if ( num[p][j] != num[k + i - p][j] ) {
						break;
					} else if ( p == ( k + i ) / 2 ) {
						ll = k - i + 1;
					}
				}
			}
			h[i][j] = hh;
			l[i][j] = ll;
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= m ; ++j ) {
			int minn = min( h[i][j] , l[i][j] );
			flag = true;
			for ( int k = i ; k <= i + minn - 1 ; ++k ) {
				if ( i + h[k][j] - 1 >= k ) {/*这个点与矩阵左上方同列, 
				它在行上的回文串的长度小于它与矩阵左上方的距离,矛盾*/ 
					minn = min( minn , h[k][j] );
				} else {
					flag = false;
					break;
				}
			}
			for ( int k = j ; k <= j + minn - 1 ; ++k ) {
				if ( j + l[i][k] - 1 >= k && flag ) {//同理 
					minn = min( minn , l[i][k] );
				} else {
					flag = false;
					break;
				}
			}
			if ( flag ) {
				ans = max( ans , minn );
			}
		}
	}
	printf("%d",ans);
	return 0;
}

T2:树环转换

Description
给定一棵N个节点的树,去掉这棵树的一条边需要消耗值1,为这个图的两个点加上一条边也需要消耗值1。树的节点编号从1开始。在这个问题中,你需要使用最小的消耗值(加边和删边操作)将这棵树转化为环,不允许有重边。
环的定义如下:
(1)该图有N个点,N条边。
(2)每个顶点的度数为2。
(3)任意两点是可达的。
树的定义如下:
(1)该图有N个点,N-1条边。
(2)任意两点是可达的。

Input
第一行是一个整数N代表节点的个数。
接下来N-1行每行有两个整数U, V(1 ≤ U, V ≤ N),表示双向边(U, V)

Output
输出把树转化为环的最小消耗值。

Sample Input

4
1 2
2 3
2 4

Sample Output

3

Data Constraint
对于20%的数据,有1≤N≤10。
对于100%的数据,有1≤N≤1000000。

简要思路:这题考察我们的思维能力,不难看出,本题的做法实际上是将树剖成一条一条的链,答案就是 2 1 + 1 2 * 链数(不算树原有的主干链,即总链数减去1) + 1 ,一条链要剖离树消耗一次,连成长链消耗一次,最后将长链连成环还有一次。具体剖树的方法就是:
1.找一个点作为树根,成为有根树(后面有用)
2.一个点如果有儿子节点向下递归
3.对于非树根节点,如果有多于1个儿子节点,就将它与父亲节点剖离,它的父亲节点计算儿子节点数量时不会再算上它,它的子树与原树分离,增加的链数为它的儿子数减去1(与任意一个儿子形成链),树根节点,如果有多于2个儿子节点同理,但增加的链数为它的儿子数减去2
4.计算,得出答案
不难看出这是最优的,至于证明吧,感性理解 ,观察这一方法,不难发现它在尽可能的减少一个节点与其父亲节点分离的数目,当一个节点要与树剖离时,减少其父亲节点的儿子节点数,降低了它的父亲节点也被剖离的概率。
至于正确性,自己画图吧。
通过上述做法,不难得出:

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 1000005
using namespace std;
int n , bcnt , tot;
int head[N];
struct node{
	int next;
	int to;
}str[3 * N];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline void insert( int from , int to ) {
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	return;
}
inline int dfs( int cur , int fa ) {
	int leaf = 0;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( sn == fa ) {
			continue;
		}
		leaf += dfs( sn , cur );
	}
	if ( leaf <= 1 ) {
		return 1;
	} else {
		if ( cur == 1 ) {
			tot += leaf - 2;
		} else {
			tot += leaf - 1;
		}
		return 0;
	}
}
int main () {
	//freopen( "cycle10.in" , "r" , stdin );
	read(n);
	int u , v;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		read(u);
		read(v);
		insert( u , v );
		insert( v , u );
	}
	dfs( 1 , 0 );
	tot = tot * 2 + 1;
	printf("%d",tot);
	return 0;
}

这份代码过不了最大的数据,递归实现会栈溢出,我们手打一个栈即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 1000005
using namespace std;
int n , bcnt , tot , top;
int head[N];
int st[N][3];//0:cur;1:fa;2:leaf
struct node{
	int next;
	int to;
}str[3 * N];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline void insert( int from , int to ) {
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	return;
}
inline int dfs() {
	st[++top][0] = 1;
	st[top][1] = 0;
	st[top][2] = 0;
	while ( top ) {
		int cur = st[top][0];
		if ( !head[cur] ) {
			int df = st[top][2] <= 1 ? 1 : 0;
			if ( !df ) {
				if ( cur == 1 ) {
					tot += st[top][2] - 2;
				} else {
					tot += st[top][2] - 1;
				}
			}
			top--;
			cur = st[top][0];
			st[top][2] += df;
			head[cur] = str[head[cur]].next;
		}
		for ( int i = head[cur] ; i ; i = head[cur] = str[i].next ) {
			int sn = str[i].to;
			if ( sn != st[top][1] ) {
				st[++top][0] = sn;
				st[top][1] = cur;
				st[top][2] = 0;
				break;
			}
		}
	}
}
int main () {
	//freopen( "cycle10.in" , "r" , stdin );
	read(n);
	int u , v;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		read(u);
		read(v);
		insert( u , v );
		insert( v , u );
	}
	dfs();
	tot = tot * 2 + 1;
	printf("%d",tot);
	return 0;
}

T3:海明距离

Description
对于二进制串a,b,他们之间的海明距离是指两个串异或之后串中1的个数。异或的规则为:
0 XOR 0 = 0
1 XOR 0 = 1
0 XOR 1 = 1
1 XOR 1 = 0
计算两个串之间的海明距离的时候,他们的长度必须相同。现在我们给出N个不同的二进制串,请计算出这些串两两之间的最短海明距离。

Input
第一个数字是整数T(T≤10),代表数据的组数。
接下来有T组数据,每组数据的第一行是一个正整数N,代表不同的二进制串的个数。接下来是N行,每行都是一个二进制串(长度是5)。我们用数字(0-9)和字符(A-F)来表示这个二进制串。它代表这个二进制串的16进制码。例如,“12345”代表的二进制串为“00010010001101000101”。

Output
对于每个数据,请输出一个整数,即答案值。

Sample Input

2
2
12345
54321
4
12345
6789A
BCDEF
0137F

Sample Output

6
7

Data Constraint
对于30%的数据有1≤N≤100
对于全部数据,有1≤N≤100000

简要思路:我们应该知道,XOR运算具有可逆性, a i a_i ^ t = a j a_j 等价于 a i a_i ^ a j a_j = t。枚举t,用迭代加深搜搜出答案,再加上细节处理即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 1050005
using namespace std;
int vis[N]/*打标记*/ , a[N];
char s[N];
int t , n;
bool flag;
inline void read( int & res ) {
	res = 0;
	int 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 int transfor( char *ss ) {
	int len = strlen(ss);
	int res = 0;
	for ( int i = 0 ; i <= len - 1 ; ++i ) {
		if ( ss[i] >= '0' && ss[i] <= '9' ) {
			res = res * 16 + ( ss[i] - '0' );
		} else {
			res = res * 16 + ( 10 + ss[i] - 'A' );
		}
	}
	return res;
}
inline bool dfs( int tem , int wei , int depth , int lim ){
	if ( depth == lim + 1 ) {
		for ( int i = 1 ; i <= n ; ++i ) {
			if ( vis[tem ^ a[i]] == t ) {
				return true;
			}
		}
		return false;
	}
	for ( int i = wei + 1 ; i <= 20 ; ++i ) {
		int mi = 1 << i;
		if ( dfs( tem + mi , i , depth + 1 , lim ) ) {
			return true;
		}
	}
	return false;
}
int main () {
	read(t);
	while ( t ) {
		flag = false;
		read(n);
		for ( int i = 1 ; i <= n ; ++i ) {
			scanf("%s",(s + 1));
			a[i] = transfor( (s + 1) );
			if ( vis[a[i]] == t ) {
				flag = true;
			}
			vis[a[i]] = t;
		}
		if ( flag ) {
			printf("0\n");
			continue;
		}
		int ans = 1;
		while ( true ) {
			if ( dfs( 0 , -1 , 1 , ans ) ) {
				break;
			}
			ans++;
		}
		printf("%d\n",ans);
		t--;
	}
	return 0;
}

T4:排列

Description
一个关于n个元素的排列是指一个从{1, 2, …, n}到{1, 2, …, n}的一一映射的函数。这个排列p的秩是指最小的k,使得对于所有的i = 1, 2, …, n,都有p(p(…p(i)…)) = i(其中,p一共出现了k次)。
例如,对于一个三个元素的排列p(1) = 3, p(2) = 2, p(3) = 1,它的秩是2,因为p(p(1)) = 1, p(p(2)) = 2, p(p(3)) = 3。
给定一个n,我们希望从n!个排列中,找出一个拥有最大秩的排列。例如,对于n=5,它能达到最大秩为6,这个排列是p(1) = 4, p(2) = 5, p(3) = 2, p(4) = 1, p(5) = 3。
当我们有多个排列能得到这个最大的秩的时候,我们希望你求出字典序最小的那个排列。对于n个元素的排列,排列p的字典序比排列r小的意思是:存在一个整数i,使得对于所有j < i,都有p(j) = r(j),同时p(i) < r(i)。对于5来说,秩最大而且字典序最小的排列为:p(1) = 2, p(2) = 1, p(3) = 4, p(4) = 5, p(5) = 3。

Input
输入的第一行是一个整数T(T <= 10),代表数据的个数。
每个数据只有一行,为一个整数N。

Output
对于每个N,输出秩最大且字典序最小的那个排列。即输出p(1), p(2),…,p(n)的值,用空格分隔。

Sample Input

2
5
14

Sample Output

2 1 4 5 3
2 3 1 5 6 7 4 9 10 11 12 13 14 8

Data Constraint
对于40%的数据,有1≤N≤100。
对于所有的数据,有1≤N≤10000。

简要思路:这题有点像DAY6的游戏,仔细一看,果不其然,思维是类似的。通过此题,不难得出,答案是n拆分出的数(1除外)的最小公倍数,因此将n拆成几个互质的数是最优的(若不互质,就浪费了一些数,浪费宝贵的资源 )。接下来,就是线性筛素数,定义状态( f [ i ] [ j ] f[i][j] 表示用到前i个质数形成数j的最优值, p r e [ i ] [ j ] pre[i][j] 表示形成最优值 f [ i ] [ j ] f[i][j] 前的状态,即形成的数),转移时枚举素数的幂即可。因为要求最小字典序,所以要将几个互质的数,包括1,从小到大排序,以从一到n的顺序分块操作,对于长为一的分块,直接输出即可;对于一个大于一的分块,最优方案为将对应原序列的第一个移到最后一位,其他位向前移(不会证)。最后说一句,本题求出的 f [ i ] [ j ] f[i][j] 会非常大,但本题只要方案,又不能取模 ,有一个大佬教我用自然对数优化,因为C++库自带求 ln \ln 的函数,所以用这方法非常方便。注意对数运算为 ln x + ln y = ln ( x y ) \ln x + \ln y =\ln (x * y)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define N 10005
using namespace std;
int t , n , pcnt , k;
double ans , maxn , f[1235][N];
int pre[1235][N] , tem[N];
int vis[N] , prime[1235];
inline void read( int & res ) {
	res = 0;
	int 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 () {
	for ( int i = 2 ; i <= 10000 ; ++i ) {
		if ( !vis[i] ) {
			prime[++pcnt] = i;
		}
		for ( int j = 1 ; j <= pcnt ; ++j ) {
			if ( i * prime[j] > 10000 ) {
				break;
			}
			vis[i * prime[j]] = 1;
			if ( !( i % prime[j] ) ) {
				break;
			}
		}
	}
	for ( int i = 0 ; i <= pcnt ; ++i ) {
		for ( int j = 0 ; j <= 10000 ; ++j ) {
			f[i][j] = pre[i][j] = 0;
		}
	}
	for ( int i = 1 ; i <= pcnt ; ++i ) {
		for ( int j = 0 ; j <= 10000 ; ++j ) {
			f[i][j] = f[i - 1][j];
			pre[i][j] = j;
			int te = prime[i];
			while ( te <= j ) {
				maxn = f[i - 1][j - te] + log((double)te);
				if ( maxn > f[i][j] ) {
					f[i][j] = maxn;
					pre[i][j] = j - te;
				}
				te *= prime[i]; 
			}
		}
	}
	read(t);
	while ( t-- ) {
		read(n);
		ans = 0;
		for ( int i = 0 ; i <= n ; ++i ) {
			maxn = f[pcnt][i];
			if ( maxn > ans ) {
				ans = maxn;
				k = i;
			}
		}
		int tot = 0;
		int cnt = 0;
		while ( k < n ) {
			printf("%d ",++cnt);
			n--;
		}
		cnt++;
		int te;
		for ( int i = pcnt ; i >= 0 ; --i ) {
			te = k - pre[i][k];
			if (te) {
				tem[++tot] = te;
			}
			k = pre[i][k];
		}
		sort( tem + 1 , tem + 1 + tot );
		for ( int i = 1 ; i <= tot ; ++i ) {
			for ( int j = cnt + 1 ; j <= cnt + tem[i] - 1 ; ++j ) {
				printf("%d ",j);
			}
			printf("%d ",cnt);
			cnt += tem[i];
		}
		printf("\n");
	}
	return 0;
}

今天又GET到了一个新技巧(自然对数优化),以后要多注意这些细节啊!

猜你喜欢

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