Kickstart Round A 2017 Problem A. Square Counting Problem B. Patterns Overlap

感想:

Kickstart Round A小计,第一次打Kickstart,据说是进google的必经之路,难度确实还比较难,更可怕的是前面厉害的人太多,第三题是计算几何碰都没怎么碰过的题,AK的人还是那么多= =,可见进google是有多么大的竞争。比赛时只能做出A和b的small,比赛完后想想B的big也是可以做的,然后就做了。

ps:第一次打这个比赛,有些规则不是很清楚,原来每次给出的数据不一样,第二题后面一直提交的是第一次下载的数据,本来程序已经没问题了,但是还是一直incorrect,坑了我好久- - 这也算是不熟悉的代价吧


Problem A. Square Counting


题意:

数一个棋盘中的正方形个数,包括斜正方形的个数。

思路:

非斜正方形个数公式:

m*(m+1)*(3*n+1-m)/6    (n>=m)

斜正方形个数公式:

(2*n-m)*(m-1)*m*(m+1)/12    (n>=m)

当然这个公式不是我推的- -,能手推出来的肯定是大牛了,因为涉及到除法取余,由于分母只有6和12,是可以不用逆元的,只需要简单的约分即可,这里就不给出代码了。


Problem B. Patterns Overlap

Alice likes reading and buys a lot of books. She stores her books in two boxes; each box is labeled with a pattern that matches the titles of all of the books stored in that box. A pattern consists of only uppercase/lowercase English alphabet letters and stars (*). A star can match between zero and four letters. For example, books with the titles GoneGirl and GoneTomorrow can be put in a box with the pattern Gone**, but books with the titles TheGoneGirl, and GoneWithTheWind cannot.

Alice is wondering whether there is any book that could be stored in either of the boxes. That is, she wonders if there is a title that matches both boxes' patterns.

Input

The first line of the input gives the number of test cases, TT test cases follow. Each consists of two lines; each line has one string in which each character is either an uppercase/lowercase English letter or *.

Output

For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is TRUE if there is a string that matches both patterns, or FALSE if not.

Limits

1 ≤ T ≤ 50.

Small dataset

1 ≤ the length of each pattern ≤ 200.
Each pattern contains at most 5 stars.

Large dataset

1 ≤ the length of each pattern ≤ 2000.

Sample


Input 
 

Output 
 
3
****
It
Shakes*e
S*speare
Shakes*e
*peare

Case #1: TRUE
Case #2: TRUE
Case #3: FALSE


In sample case #1, the title It matches both patterns. Note that it is possible for a * to match zero characters.

In sample case #2, the title Shakespeare matches both patterns.

In sample case #3, there is no title that matches both patterns. Shakespeare, for example, does not work because the * at the start of the *peare pattern cannot match six letters.


题意:

给出两个字符串,可以理解为正则串,包含大小写字母和*,*能匹配0~4个任意字符,问是否存在一个字符串,这两个字符串都可以表示。


思路:(参考,如有更好的解法,欢迎留言)

动态规划,构造状态dp[i][j]:第一个字符串到位置i,第二个字符串到位置j,能否表示成功。

转移:dp[i][j]能转移到哪些状态呢,如果不是*的情况,则转移到dp[i+1][j+1],考虑是str1[i+1]是*的情况,则可以转移到dp[i+1][k],只要j~k之间的非*字母不超过4个即可,千万不要以为只有j+1到j+5哦,这题正确率很低原因应该就是这个,于是到这里small就可以写出来了,这个复杂度最差近似O(n^3)。

代码1:直接转移,适合small

const int maxn=2010;
char str1[maxn],str2[maxn];
bool dp[maxn][maxn];

void solveBsmall(){
    int t;
    scanf("%d",&t);
    for (int ca = 1; ca <=t ; ++ca) {
        scanf("%s%s",str1+1,str2+1);
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        int n=strlen(str1+1);
        int m=strlen(str2+1);
        for (int i = 0; i <=n ; ++i) {
            for (int j = 0; j <=m ; ++j) {
                if(!dp[i][j]) continue;
                if(i<n&&j<m&&str1[i+1]!='*'&&str2[j+1]!='*'){
                    if(str1[i+1]==str2[j+1]) dp[i+1][j+1]=true;
                }
                if(i<n&&str1[i+1]=='*'){
                    int cnt=0;
                    for (int k = 0; j+k <=m; ++k) {
                        if(k>0&&str2[j+k]!='*') cnt++;
                        if(cnt==5) break;
                        dp[i+1][j+k]=true;
                    }
                }
                if(j<m&&str2[j+1]=='*'){
                    int cnt=0;
                    for (int k = 0; i+k <=n ; ++k) {
                        if(k>0&&str1[i+k]!='*') cnt++;
                        if(cnt==5) break;
                        dp[i+k][j+1]=true;
                    }
                }
            }
        }
        if(dp[n][m]) printf("Case #%d: TRUE\n",ca);
        else printf("Case #%d: FALSE\n",ca);
    }
}


big思路,将*扩展成4个*,这样就能正常转移了,技巧值得学习,代码这里就不给出了。


以下是比赛时想到思路,仅供扩充思维。

而如果要解决big的话,这样的复杂度肯定是不行的,首先需要知道一个点之后的不超过4个字母的点是哪个点,用两个指针遍历一下就可以知道了,存在next数组里。

因为每次更新的是一个区间j~next[j],于是想到了用线段树来维护一下dp,区间更新,单点查询,当然这里就要维护n+m颗线段了,想想还是有点害怕的,不过思路清晰一点不手残写出来后代码也还是不多的。

代码2:(线段树维护dp)

const int maxn=2010;
char str1[maxn],str2[maxn];
int next1[maxn],next2[maxn];
bool first[maxn][maxn*4];
bool second[maxn][maxn*4];

void getnext(char str[],int next[]){
    int n=strlen(str+1);
    int cnt=0,ed=n;
    for (int i = 1; i <=n ; ++i) {
        if(str[i]!='*') cnt++;
        if(cnt==5) {
            ed=i-1;
            break;
        }
    }
    next[0]=ed;
    for (int i = 1; i <=n ; ++i) {
        if(str[i]=='*') next[i]=ed;
        else{
            ed++;
            while(ed<=n&&str[ed]=='*'){
                ed++;
            }
            ed=min(ed,n);
            next[i]=ed;
        }
    }
}

bool query(int le,int ri,int rt,int pos,bool arr[maxn][maxn*4],int index){
    if(arr[index][rt]) return true;
    if(le==ri){
        return false;
    }
    int mid=(le+ri)>>1;
    if(pos<=mid) return query(le,mid,rt<<1,pos,arr,index);
    else return query(mid+1,ri,rt<<1|1,pos,arr,index);
}

void update(int le,int ri,int rt,int ql,int qr,bool arr[maxn][maxn*4],int index){
    if(arr[index][rt]) return ;
    if(le==ql&&ri==qr){
        arr[index][rt]=true;
        return ;
    }
    int mid=(le+ri)>>1;
    if(qr<=mid){
        update(le,mid,rt<<1,ql,qr,arr,index);
    }
    else if(ql>mid){
        update(mid+1,ri,rt<<1|1,ql,qr,arr,index);
    }
    else {
        update(le,mid,rt<<1,ql,mid,arr,index);
        update(mid+1,ri,rt<<1|1,mid+1,qr,arr,index);
    }
}
void solveB_plan1(){
    int t;
    scanf("%d",&t);
    for (int ca = 1; ca <=t ; ++ca) {
        scanf("%s%s",str1+1,str2+1);
        int n=strlen(str1+1);
        int m=strlen(str2+1);
        getnext(str1,next1);
        getnext(str2,next2);
        memset(first,0,sizeof(first));
        memset(second,0,sizeof(second));
        update(0,m,1,0,0,first,0);

        for (int i = 0; i <=n ; ++i) {
            for (int j = 0; j <=m ; ++j) {
                if(!query(0,m,1,j,first,i)&&
                        !query(0,n,1,i,second,j)) continue;
                if(i<n&&j<m&&str1[i+1]!='*'&&str2[j+1]!='*'){
                    if(str1[i+1]==str2[j+1]) update(0,m,1,j+1,j+1,first,i+1);
                }
                if(i<n&&str1[i+1]=='*'){
                    update(0,m,1,j,next2[j],first,i+1);
                }
                if(j<m&&str2[j+1]=='*'){
                    update(0,n,1,i,next1[i],second,j+1);
                }
            }
        }
        if(query(0,m-1,1,m,first,n)|| query(0,n-1,1,n,second,m))
            printf("Case #%d: TRUE\n",ca);
        else printf("Case #%d: FALSE\n",ca);
    }
}


后面想了想,查询只是单点查询,用线段树是不是有点大材小用了,非*转移的状态用dp数组维护,如果是通过*转移的状态,用set把以某个点(i+1)开始的能转移的点(j)记录下来不就可以了么,这样每次判断哪个状态是否可以,只用查找set中比这个位置pos小的第一个点p,如果pos在p~next[p]之间, 就是合法的状态。

代码3:(用map来维护dp)

bool dp[maxn][maxn];
bool isok(int i,int j,map<int,set<int>>&mp1,map<int,set<int>>&mp2){
    set<int>::iterator it;
    it=mp1[i].upper_bound(j);
    if(it!=mp1[i].begin()){
        it--;
        if(next2[(*it)]>=j) return true;
    }
    it=mp2[j].upper_bound(i);
    if(it!=mp2[j].begin()){
        it--;
        if(next1[(*it)]>=i) return true;
    }
    return false;
}
void solveB_plan2(){
    int t;
    scanf("%d",&t);
    for (int ca = 1; ca <=t ; ++ca) {
        scanf("%s%s",str1+1,str2+1);
        int n=strlen(str1+1);
        int m=strlen(str2+1);
        getnext(str1,next1);
        getnext(str2,next2);
        memset(dp,0, sizeof(dp));
        dp[0][0]=true;
        map<int,set<int>>mp1,mp2;
        for (int i = 0; i <=n ; ++i) {
            for (int j = 0; j <=m ; ++j) {
                if(!dp[i][j]&&!isok(i,j,mp1,mp2)) continue;
                if(i<n&&j<m&&str1[i+1]!='*'&&str2[j+1]!='*'){
                    if(str1[i+1]==str2[j+1]) dp[i+1][j+1]=true;
                }
                if(i<n&&str1[i+1]=='*'){
                    mp1[i+1].insert(j);
                }
                if(j<m&&str2[j+1]=='*'){
                    mp2[j+1].insert(i);
                }
            }
        }
        if(dp[n][m]||isok(n,m,mp1,mp2)) printf("Case #%d: TRUE\n",ca);
        else printf("Case #%d: FALSE\n",ca);
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值