感想:
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, T. T 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
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);
}
}