前言
你们可能会觉得奇怪,为什么这场比赛会突如其来,原因很简单,今天原本是开讲座的,但由于神犇lkf的讲座过于高深,曲高和寡,很少人听(蒟蒻本色
Q
W
Q
QWQ
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] h[i][j]表示从第 i i i行第 j j j列这一位置往右(同一行)最长的回文串的长度,用 l [ i ] [ j ] l[i][j] l[i][j]表示从第 i i i行第 j 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
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 ai ^ t = a j a_j aj 等价于 a i a_i ai ^ a j a_j aj = 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]
f[i][j]表示用到前i个质数形成数j的最优值,
p
r
e
[
i
]
[
j
]
pre[i][j]
pre[i][j]表示形成最优值
f
[
i
]
[
j
]
f[i][j]
f[i][j]前的状态,即形成的数),转移时枚举素数的幂即可。因为要求最小字典序,所以要将几个互质的数,包括1,从小到大排序,以从一到n的顺序分块操作,对于长为一的分块,直接输出即可;对于一个大于一的分块,最优方案为将对应原序列的第一个移到最后一位,其他位向前移(不会证)。最后说一句,本题求出的
f
[
i
]
[
j
]
f[i][j]
f[i][j]会非常大,但本题只要方案,又不能取模 ,有一个大佬教我用自然对数优化,因为C++库自带求
ln
\ln
ln的函数,所以用这方法非常方便。注意对数运算为
ln
x
+
ln
y
=
ln
(
x
∗
y
)
\ln x + \ln y =\ln (x * y)
lnx+lny=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到了一个新技巧(自然对数优化),以后要多注意这些细节啊!