2017暑假第二阶段第八场 总结

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


比较常见的思路是把原数写成 ak1Bk1+ak2Bk2++a1B1+a0B0 的形式,然后就做不起了,只能暴力转进制搞了。

不妨换一种考虑的方向。考虑原数转换为B进制之后的位数,从而优雅地暴力。

首先转换后只有一位数肯定不是最优解,因为这样的话B满足B>N,然而在B=N-1时,一定是两位的回文数 (11)N1 (对于数字2除外,因为并没有一进制)。

如果转换后是两位数,那么一定是 (AA)B 的形式,那么满足 N=A×B+A=A(B+1) ,那么从大到小枚举因数 A 即可。

如果转换后是三位数,那么最小也是(101)B,所以有 NB2+1 ,根据 N 的范围,B最大仅为 105 ,大于三位数之后 B 的范围肯定更小,在这个数据范围暴力枚举是可以接受的。

于是得出解法:首先在N以内暴力寻找是否有这样的 B ;如果找到就停止,如果没找到就枚举N的因数。但是要注意,这样的因数必须要大于 N 才是符合要求的,因为 N 以内的 B 已被枚举过,且被验证为不满足要求。

#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


方向对了就是道水题,如果不对什么错解都能想出来。

正解是差分约束。所有的区间的数据都可以看成前缀和相减的一个不等式。由于一个编号的人一定有一个,不会存在负数个人(废话),此外的约束条件还有:0sumisumi11

一开始还想写个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主要是提醒了差分约束这种算法,太久没有用了,确实错得应该。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值