Codeforces Good Bye 2015 (A,B,C,D)
tags : Codeforces
A. New Year and Days (水题)
题意
如果输入是”k of week”(1<=k<=7),输出2016年有多少个星期k.
如果输入是”k of month”(1<=k<=31),输出2016年有多少个k号.
题目中有提示,2016年1月1日是周五。
解析
水题,随便数。
代码
/**
* Created by 0xLLLLH on 2016/1/18.
* Email : linlihao159@gmail.com
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>
using namespace std;
#define MAXN 100000+100
int main()
{
int k;
char type[100];
scanf("%d of %s",&k,type);
int ans = 0;
if (strcmp(type, "week") == 0)
{
ans=366 / 7;
if (k < 5 && 5 + 366 % 7 - 7 >= k)
ans++;
else if (k >= 5 && 5 + 366 % 7 > k)
ans++;
}
else if (strcmp(type, "month") == 0)
{
if (k <= 29)
ans = 12;
else if (k == 30)
ans = 11;
else if (k == 31)
ans = 7;
}
printf("%d\n",ans);
return 0;
}
B.New Year and Old Property (二进制)
题意
输出[l,r]范围内满足二进制表示中只有一个0
的数字的个数。
解析
简单二进制运算,通过(1LL << i)-1
得到二进制为i个1的数,然后尝试减去(1LL<<j)
,得到满足二进制表示中只有一个0
的数,统计在[l,r]范围内的数目即可。
代码
/**
* Created by 0xLLLLH on 2016/1/18.
* Email : linlihao159@gmail.com
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>
using namespace std;
#define MAXN 100000+100
int main()
{
long long l, r;
scanf("%lld%lld",&l,&r);
int cnt = 0;
for (int i = 2; i < 63; i++)
{
for (int j = 0; j < i-1; j++)
{
long long x = (1LL << i) - 1 - (1LL << j);
if (x >= l&&x <= r)
cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
C.New Year and Domino (前缀和)
我写的时候脑抽了选择使用
全部组合数目-不合法组合数目
的方法来计算,效果与直接算合法数目相同,但完全是多此一举。
题意
给你一个由’.’和’#’组成的地图(h*w),以及q次查询。对于每次查询,输出以(x1,y1)为左上角,以(x2,y2)为右下角的矩形中有多少对相邻的’.’。
解析
主要的想法就是利用前缀和。
对于每一行,统计本行从开始到当前位置为止的行相邻的’.’有多少对(前缀和思想)得到
Tij
。然后,再次利用前缀和思想,实际存储的是从第一行开始到当前行为止对应位置的
Tij
的和,记为
Rij
。
对于每一列,我们可以用同样的方法得到对应的
Cij
然后,对于每次询问,我们可以使用 (Rx2,y2−Rx1−1,y2)−(Rx2,y1−Rx1−1,y1) 计算出询问矩阵中合法的行方向组合的数目。同理,可以得到列方向上的组合的数目,相加即可得到答案.
队友说可以不用求第二次前缀和,而是每次循环的时候去统计,,,我一直以为这样会超时来着。
代码
/**
* Created by 0xLLLLH on 2016/1/18.
* Email : linlihao159@gmail.com
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>
using namespace std;
#define MAXN 100000+100
char mp[555][555];
int row[555][555];
int col[555][555];
int main()
{
int h, w;
scanf("%d%d",&h,&w);
getchar();
for (int i = 1; i <= h; i++)
scanf("%s", &mp[i][1]);
memset(row,0,sizeof (row));
memset(col, 0, sizeof(col));
for (int i = 1; i <= h; i++)
{
//求当前行方向不合法的数目
for (int j = 1; j <= w; j++)
{
if (j == 1)
row[i][j] = 0;
else
{
row[i][j] = row[i][j - 1];
if ((mp[i][j] == '#') || (mp[i][j - 1] == '#')) row[i][j]++;
}
}
//累加成前缀和
if (i != 1)
for (int j = 1; j <= w; j++)
row[i][j] += row[i-1][j];
}
for (int i = 1; i <= w; i++)
{
for (int j = 1; j <= h; j++)
{
//求当前列方向上不合法的数目
if (j == 1)
col[j][i] = 0;
else
{
col[j][i] = col[j - 1][i];
if ((mp[j][i] == '#') || (mp[j - 1][i] == '#')) col[j][i]++;
}
}
if (i != 1)
for (int j = 1; j <= h; j++)
col[j][i] += col[j][i-1];
}
int q;
scanf("%d", &q);
while (q--)
{
long long x1, y1, x2, y2;
scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
long long s = (x2 - x1)*(y2 - y1 + 1) + (x2 - x1 + 1)*(y2 - y1);
long long ban_row = (row[x2][y2] - row[x1-1][y2]) - (row[x2][y1] - row[x1-1][y1]);
long long ban_col = (col[x2][y2] - col[x2][y1-1]) - (col[x1][y2] - col[x1][y1-1]);
printf("%lld\n",s-ban_row-ban_col);
}
return 0;
}
D.New Year and Ancient Prophecy (DP)
这题想了一下午,终于整体的dp想对了,结果一发直接TLE,后来经过队友提醒才知道要预处理公共前缀,而不是像我之前那样傻傻的进行字符串比较。
题意
将一个由数字组成的字符串分为许多段(不改变顺序),要求子串满足:
- 每一个子串都代表一个正整数
- 子串代表的数字严格递增
- 子串没有前导0
求所有满足要求的划分数,输出结果对 (109+7) 取模的结果。
解析
暴力显然不行,所以我们使用DP来解。
首先,使用dp[i][j]
储存到第i个字符为止,划分后数字最大长度不超过j的方案数。
然后,对于每个位置i,设第二大数字为num1,起始位置s1,最大的数字为num2,起始位置s2。
然后首先要知道,如果len(num1) < len(num2)
,则定有num1 < num2
。除此之外,如果len(num1)==len(num2)
且num1的字典序比num2小,也有num1 < num2
。其他情况都是不满足要求的。
对于每个结束位置i,长度j的num2,其方案数dp[i][j]
应为num1取不同长度的时候的方案数之和,也就是dp[i-j][j]
或者dp[i-j][j-1]
。(我们的dp[i][j]
存的就是累计值)
所以有以下状态转移方程式:
其中
需要注意的是,直接使用strcmp()或类似的方法比较字典序会超时。我们可以先预处理出字符串本身不同位置间的最长公共前缀comm[x][y]
,然后以
O(1)
的复杂度比较。
代码
/**
* Created by 0xLLLLH on 2016/1/18.
* Email : linlihao159@gmail.com
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 5000+100
#define MOD 1000000007
int n;
char str[MAXN];
//储存到第i个字符为止,划分后子串最大长度不超过j的方案数
long long dp[MAXN][MAXN];
long long comm[MAXN][MAXN];
//用于比较字符串
int scmp(int s1, int s2, int len)
{
long long co = comm[s1][s2];
if (co >= len)
return 0;
else
{
if (str[s1 + co] < str[s2 + co])
return -1;
else if (str[s1 + co] > str[s2 + co])
return 1;
}
return 0;
}
//预处理出str中位置i和位置j的前缀长度
void getCommomPrefix()
{
for (int i = 0; i <= n; i++)
{
comm[i][n] = 0;
comm[n][i] = 0;
}
for (int i = n-1; i >=0; i--)
{
for (int j = n-1; j >=0; j--)
{
comm[i][j] = comm[i + 1][j + 1];
if (str[i] == str[j])
comm[i][j]++;
else
comm[i][j] = 0;
}
}
}
int main()
{
scanf("%d", &n);
getchar();
scanf("%s",str);
getCommomPrefix();
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; i++)
{
if (i == 0)
{
dp[0][0] = 0;
for (int j = 1; j <= n; j++)
dp[0][j] = 1;
}
else
{
for (int len = 1; len<=n; len++)
{
dp[i][len] = dp[i][len - 1]; //dp[i][j]存的是长度0..len的累计值,要先赋初值为长度len-1时的值
int s1 = i - len * 2 + 1; //第二长的字符串的起始位置(假设其长度为len)
int s2 = i - len + 1; //最后,也是最长的字符串的起始位置
if (s2 < 0 || str[s2] == '0')
continue;
if (s1 >= 0) //如果第二长的字符串长度能取到len
{
if (str[s2] != '0') //字符串不能有前导0
{
if (str[s1] != '0'&&scmp(s1, s2, len) < 0)
dp[i][len] += dp[s2-1][len];
else
dp[i][len] += dp[s2-1][len - 1];
dp[i][len] %= MOD;
}
}
else //取不到len,则最长为s2-1
{
if (s2 == 0)
dp[i][len]++;
else
dp[i][len] += dp[s2 - 1][len];
dp[i][len] %= MOD;
}
}
}
}
printf("%lld\n",dp[n-1][n]);
return 0;
}