T1 回文数
问题描述
给你一个数N,求出最小的B(B>=2),使得 N在 B进制下为回文数。
输入格式
第一行1个整数TEST,表示数据组数。 接下来TEST行,每行一个整数N。
输出格式
共输出TEST行,每行对应一个答案B
数据范围
30%的数据 TEST<=10,N<=10 4 。
100%的数据 TEST<=1,000,N<=10 10 。
比较常见的思路是把原数写成 ak−1Bk−1+ak−2Bk−2+……+a1B1+a0B0 的形式,然后就做不起了,只能暴力转进制搞了。
不妨换一种考虑的方向。考虑原数转换为B进制之后的位数,从而优雅地暴力。
首先转换后只有一位数肯定不是最优解,因为这样的话B满足B>N,然而在B=N-1时,一定是两位的回文数 (11)N−1 (对于数字2除外,因为并没有一进制)。
如果转换后是两位数,那么一定是 (AA)B 的形式,那么满足 N=A×B+A=A(B+1) ,那么从大到小枚举因数 A 即可。
如果转换后是三位数,那么最小也是
于是得出解法:首先在
#include<stdio.h>
#include<cmath>
#define ll long long
using namespace std;
ll N;
int a[65];
bool check(int b)
{
if(!N)return true;
int cnt=0,i;
ll tmp=N;
while(tmp)
{
a[++cnt]=tmp%b;
tmp/=b;
}
for(i=1;i<=cnt-i+1;i++)if(a[i]!=a[cnt-i+1])return false;
return true;
}
int main()
{
int T;
ll i,tmp;
bool flag;
scanf("%d",&T);
while(T--)
{
flag=false;
scanf("%lld",&N);
if(N==2){puts("3");continue;}
tmp=ceil(sqrt(N));
for(i=2;i<=tmp;i++)
{
if(check(i))
{
flag=true;
printf("%lld\n",i);
break;
}
}
if(flag)continue;
for(i=tmp-1;i>=1;i--)
{
if(N%i==0&&(N/i-1)>tmp)
{
printf("%lld\n",N/i-1);
break;
}
}
}
}
T2 奶牛阵列
问题描述
每天早晨约翰的奶牛都会在挤奶的时候排成阵列,即站成R(1<=R<=10000)行C(1<=C<=75)列的矩阵。我们知道,约翰是奶牛专家,他打算写一本关于喂养奶牛的书,他发现,当奶牛按不同血统标记以后,整个大矩阵就像由很多小矩阵无缝拼接的一样。
请帮助约翰找到面积最小的模型矩阵,使他能拼出整个大矩阵,当然,模型矩阵的尺寸不一定能整除大矩阵,也就是说你可以用若干个模型矩阵,拼出一个包含大矩阵的更大的矩阵。
输入格式
第一行, 两个整数R和C
接下来是由大写字母构成的R*C的矩阵
输出格式
一个整数,表示最小模型矩阵的面积。
样例输入
2 5
ABABA
BABAB
样例输出
4
样例说明:
模型矩阵如下:
AB
BA
拼出的大矩阵如下:
ABABAB
BABABA
根据题意,应该是不能把小矩形重叠放置的。但是由于本题数据太弱,很多错解都能AC。对于错解,这里不一一列举,正解是KMP。
不妨先把每一列考虑成一个字符,再对这些“字符”通过KMP找出“最小循环节”,即最小循环矩形,并记录长度;再把每一行也考虑成一个字符,并进行相同的操作。那么答案就是两个“长度”的乘积。KMP找最小循环矩形的正确性是显然的,求答案的操作可以通过下面的图理解:
将字符串转换为“字符”考虑后,我们仅关心两个“字符”是否相同,那么可以使用hash。如果害怕hash出错,也可以开两个hash表。但是由于数据本身太弱,这里就只开了一个。
#include<stdio.h>
#define ll long long
#define mod 0xffff
int R,C,fail[10005],rans,cans;
ll Hash[10005];
char ch[10005][80];
int main()
{
int i,j,sd=131;
ll h;
scanf("%d%d",&R,&C);
for(i=1;i<=R;i++)scanf("%s",&ch[i][1]);
for(i=1;i<=R;i++)
{
h=0;
for(j=1;j<=C;j++)h=(h+ch[i][j])*sd%mod;
Hash[i]=h;
}
fail[1]=j=0;
for(i=2;i<=R;i++)
{
while(j>0&&Hash[j+1]!=Hash[i])j=fail[j];
if(Hash[j+1]==Hash[i])j++;
fail[i]=j;
}
rans=R-fail[R];
for(i=1;i<=C;i++)
{
h=0;
for(j=1;j<=R;j++)h=(h+ch[j][i])*sd%mod;
Hash[i]=h;
}
fail[1]=j=0;
for(i=2;i<=C;i++)
{
while(j>0&&Hash[j+1]!=Hash[i])j=fail[j];
if(Hash[j+1]==Hash[i])j++;
fail[i]=j;
}
cans=C-fail[C];
printf("%d",cans*rans);
}
提供几组肉眼就能看出答案的小数据,能够卡掉很多AC代码。
/*
data1:
3 9
CBABCCBBC
CACBAACCA
BACCCAABB
正解27
data2:
2 6
BACBAC
CBCBAC
正解12
data3:
5 7
BAAAABB
ABABABA
ABABAAB
BAAAAAA
BBABABB
正解35
data4:
2 8
AAABCAAA
ABABABAB
正解12
*/
T3 突击队
问题描述
何老板的公司有t名员工,编号1到t.
何老板将他们分成了n组,每组员工的编号都是连续的,比如第i组[Ai,Bi]表示编号Ai,Ai+1,…,Bi都属于该组。
现何老板接到一个大单,工期很紧,需要组成一个突击组来完成任务,他想在每个组里抽出一些员工来组成突击组。现在告诉你每个组至少要抽出的人数,问,该突击组的人数最少是多少?
输入格式
第一行,一个整数n
接下来n行,每行三个整数Ai,Bi,Ci,描述一个组的情况,其中Ai,Bi表示该组员工编号的范围,Ci表示改组中至少要抽出Ci个人去突击组。
输出格式
一个整数,表示所求的答案。
数据范围
0 <= t <= 50000
1 <= n <= 50000
ai <= bi
1 <= ci <= bi - ai+1
方向对了就是道水题,如果不对什么错解都能想出来。
正解是差分约束。所有的区间的数据都可以看成前缀和相减的一个不等式。由于一个编号的人一定有一个,不会存在负数个人(废话),此外的约束条件还有:
一开始还想写个Dijkstra+Heap,结果忘了Dijkstra不能处理负权边……
#include<stdio.h>
#include<queue>
#include<cstring>
#define Max(x,y) ((x>y)?(x):(y))
#define MAXN 50005
#define MAXM 150005
using namespace std;
int N,T;
int en[MAXM],nex[MAXM],las[MAXN],len[MAXM],tot;
void ADD(int x,int y,int z)
{
en[++tot]=y;
nex[tot]=las[x];
las[x]=tot;
len[tot]=z;
}
int dis[MAXN];
bool mark[MAXN];
void SPFA(int s)
{
int i,x,y;
memset(dis,-60,sizeof(dis));
queue<int>Q;
Q.push(s);
dis[s]=0;
while(Q.size())
{
x=Q.front();Q.pop();mark[x]=false;
for(i=las[x];i;i=nex[i])
{
y=en[i];
if(dis[y]<dis[x]+len[i])
{
dis[y]=dis[x]+len[i];
if(!mark[y])mark[y]=true,Q.push(y);
}
}
}
}
int main()
{
int i,x,y,z;
scanf("%d",&N);
for(i=1;i<=N;i++)
{
scanf("%d%d%d",&x,&y,&z);
ADD(x-1,y,z);
T=Max(T,y);
}
for(i=1;i<=T;i++)
{
ADD(i,i-1,-1);
ADD(i-1,i,0);
}
SPFA(0);
printf("%d",dis[T]);
}
总结
这次考试又很爆炸,只不过收获很大,至少暴露了很多知识点的不熟悉。
T1打表找规律太久,结果发现压根就没有规律,要学会更优雅的暴力解法,思考问题的角度也要多样化,不能江化;T2正解思考的方式也很值得借鉴,KMP算法还要加强;T3主要是提醒了差分约束这种算法,太久没有用了,确实错得应该。