本次比赛题目按照通过人数升序依次为:A B F C G J H E I D。
测试地址:计蒜客
官方数据、题面、题解
题意简述
给出四个整数:A,B,K,C,A,B,C 都是大于 0 的一位数,问在所有仅由 A 或 B 组成的 K 位数中(K 位数的每一位都是 A 或 B),数字 C 的个数有多少。
解题思路
首先要由简单情况考虑起,对于 C != A && C != B 的,答案就是 0; 对于 C = A = B 的,答案就是 k。否则对于任意一个 k 位数,由于每位都有 2 种选择,共 2 k 2^k 2k 种选择,总体来说是共有 2 k 2^k 2k 个不同的 k 位数。
这 2 k 2^k 2k 个数中,第一位等于 C 的共有 2 k − 1 2^{k-1} 2k−1 个,第二位等于 C 的共 2 k − 1 2^{k-1} 2k−1 个,同理第三位,第四位,… ,第 k 位等于 C 的都是 2 k − 1 2^{k-1} 2k−1 个,故最终答案为 k ∗ 2 k − 1 k * 2^{k-1} k∗2k−1 。
代码示例
#include
using namespace std;
const int P = 1e9+7;
typedef long long ll;
ll a,b,c,k;
ll qpow(ll a,ll b,ll p){
ll res = 1;
if(b < 0) return 0; //当 k = 0 时特判
while(b){
if(b&1) res = res*a%p;
a = a*a%p;
b >>= 1;
}
return res;
}
int main(){
scanf("%lld%lld%lld%lld",&a,&b,&k,&c);
if(c != a && c != b){puts("0"); return 0;}
else if(a == b && a == c){printf("%d\n",k); return 0;}
printf("%lld\n",k*qpow(2,k-1,P)%P);
return 0;
}
题意简述
计算字符串的价格。给多个字符串,每个串占一行。字符串分两种,一种字符串名为 Raked Line 只含有 C 个 ‘|’ 字符,这种字符串的价格定义为 42 * C。另一种字符串名为 PricedLine,格式 是以数字 price 开头、中间用两个字符“,-“连接,结尾是连续 C 个‘|’。,这种字符串的价格 定义为 price * C,若结尾没有‘|’出现,则 C 默认为 1 个。计算所有字符串的总价,总价向上 取整到 10 的倍数。
解题思路
本题是一道阅读理解题,读懂题意后代码就很简单。可以将计算价格的代码封装为一个函数 calc() ,最后只需要每读入一个价格就调用一次即可。不要忘了答案向上取整到十位。
代码示例
#include
using namespace std;
typedef long long ll;
inline bool isNum(char ch){
return ch >= '0' && ch <= '9';
}
int calc(const string& str){
if(isNum(str[0])){
int res = 0, i = 0;
for(;isNum(str[i]);i++) res = res*10+str[i]-'0';
int len = str.length()-i-2;
return len?len*res:res;
}else return 42*str.length();
}
int main(){
string str; ll ans = 0;
while(cin >> str) ans += calc(str);
if(ans%10) ans += 10-ans%10;
cout << ans << ",-" << endl;
return 0;
}
题意简述
给定一个圆的圆心(x,y)和半径 r,再给出一个矩形的两点坐标,试求出该矩形与圆相交的面积。
解题思路
用圆和矩形相交面积的模板来求。
题意简述
给定一个无自环、无重边的有向图,要求在保证“从源点可以到达任意点,从任意点可以到达汇点”的情况下,最多能删掉几条多余的边。 其中,源点是图中入度为零的点,汇点是图中出度为零的点。
解题思路
二分图问题。
代码示例
题意简述
给定两个字符串,字符串仅包含数字和小写字母。可以进行如下操作:
输出最小的操作次数,可以使得这两个字符串相等。
解题思路
对比普通的“编辑距离”问题,这题唯一的区别就在于如果遇到数字,则必须将它替换为任意的小写字母,也就是说有几个数字首先就需要进行几次操作。我们可以先预处理,使得数字全替换为对应长度的 ‘#’ ,代表通配符,这样就可以转化为“编辑距离”问题了。
需要注意的是如果有连续的数字,并不是将其看作十进制整体,而是全看作单独的数字。
设 f[i ,j] 表示“字符串 a 的前 i 个字符和字符串 b 的前 j 个字符匹配最少需要多少次操作”。注意空间大小!!!
代码示例
#include
using namespace std;
string a,b;
string str1 = "#",str2 = "#";
int cnt = 0; //拓展数字几次
string epd(int x){
string s = ""; cnt++;
for(int i = 1;i <= x;i++) s += '#';
return s;
}
int f[20007][2007];
void solve(){
int n = str1.length()-1, m = str2.length()-1;
for(int i = 1;i <= n;i++) f[i][0] = i;
for(int i = 1;i <= m;i++) f[0][i] = i;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++){
f[i][j] = min(f[i][j-1],f[i-1][j])+1;
if(str1[i] == str2[j] || str1[i] == '#' || str2[j] == '#')
f[i][j] = f[i-1][j-1];
}
printf("%d\n",f[n][m]+cnt);
}
int main(){
cin >> a >> b;
//预处理,展开所有数字
for(int i = 0;a[i];i++)
if(a[i] >= '0' && a[i] <= '9') str1 += epd(a[i]-'0');
else str1 += a[i];
for(int i = 0;b[i];i++)
if(b[i] >= '0' && b[i] <= '9') str2 += epd(b[i]-'0');
else str2 += b[i];
//调用solve函数解决
solve();
return 0;
}
题意简述
一维坐标轴上有 n 个点,取值范围是 [-1e6, 1e6] ,请问将这 n 个点 移动到相邻两点间隔为 k ,最少需要移动的总距离是多少。
解题思路
刚开始想利用动态规划,设 f[i , 0] 表示第 i 个点不动,只移动前 i 个点的最小移动总距离,f[i , 1] 表示第 i 个点移动时最小总距离,再利用 pos[i ,1|0] 来辅助记录位置;但是这样是错误的,因为当移动总距离相同时,i 可能有好几种不同的位置,但是上述做法显然只能记录一个,而哪一个是最优的是有后效性的。
再考虑用三分求解。题意是求一个最优解,即移动总距离最小,易得起点无论是左移还是右移都会使得结果发生变化,且这个变化是有一定的凹函数性质的,即一旦移动过了某个点,总距离就会由递减变为递增,因此可以用三分法求解。
接下来是数据范围分析,最终答案可能是 1e12 * 1e6 = 1e18,所以一旦三分的区间过大(例如 INF = 1e14)则会爆 long long。而可行域显然是 [-1e12 , 1e12] 。
代码示例
#include
using namespace std;
const int N = 1e6+10;
int n,k,a[N];
typedef long long ll;
const ll INF = 1e12;
ll Abs(ll x){return x>0?x:-x;}
ll check(ll m){//统计以 m 为起点时的最短移动总距离
ll res = 0;
for(int i = 1;i <= n;i++) res += Abs(a[i]-m),m += k;
return res;
}
void solve(){
ll l = -INF,r = INF;
while(l+1 < r){ //起点是左移还是右移
ll lm = l+r>>1, rm = lm+r>>1;
if(check(lm) < check(rm)) r = rm;
else l = lm;
}
printf("%lld\n",check(l)); //最后答案是在 l 位置
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i++) scanf("%d",a+i);
sort(a+1,a+1+n); //先排序
solve();
return 0;
}
题意简述
给定一个字符串 s ,从中选定一个最长的子串,使得该子串的字符通过重新组合(任意顺序)可以成为一个回文串,输出这个子串的长度。
解题思路
一个字符串可以组合成一个回文串,有两种可能,一种是长度为偶数,此时要求所有字符数量都是偶数;第二种是长度为奇数,此时有且仅有一个字符数量为奇数,其它都是偶数。
于是易得我们需要的仅仅是某一子串中各个字符的奇偶情况,因为 a~t 共20个字符,所以我们可以用 21 个二进制位存放 s 的前缀,位置为 0 表示该字符有偶数个,1 表示有奇数个,那么就可以在 O(1) 的时间内转移。再用一个标记数组记录每一个整数(二进制组成的十进制整数) 第一次出现的位置,接着对于每个 x (当前的整数),只要挨个遍历只有一个二进制位与它不同的所有前缀即可。
代码示例
#include
using namespace std;
const int N = 1<<21;
int vis[N],n,ans = 0;
char str[N];
void solve(){
memset(vis,-1,sizeof vis); vis[0] = 0;
for(int i = 1,x = 0;i <= n;i++){
x ^= (1<<(str[i]-'a'));
if(vis[x] != -1) ans = max(ans,i-vis[x]);
for(int j = 0,tmp;j < 21;j++){
tmp = x^(1<<j);
if(vis[tmp] != -1) ans = max(ans,i-vis[tmp]);
}
//cout << x << " " << vis[x] << endl;
if(vis[x] == -1) vis[x] = i;
}
printf("%d\n",ans);
}
int main(){
scanf("%d",&n); scanf("%s",str+1);
solve();
return 0;
}
题意简述
在一个 m * n 的矩阵上,每个方块由不同元素构成,有如下规则:
而不同方块之间的转化规则为:
给出初始图,输出 T 回合后的状态。
解题思路
应该是模拟题,但是情况比较多,有些复杂。每个方块可能由 4 种状态(草地,墓地,光秃秃,自从上次被吃后经过了几回合),而羊和狼都有三种(是什么生物,死了吗,所在位置,上次吃饭是什么时候)。
搞了十几个小时总是改了这个错了那个,有些搞不懂题意到底是怎么表述的了,有的数据是狼先走,羊后走,有的是狼和羊同时走,直接放标程了。
代码示例
#include
using namespace std;
using ii = pair<int,int>;
#define F(i,a,b) for (int i = (int)(a); i < (int)(b); ++i)
const int GRASS_TIMER = 3;
const int SHEEP_TIMER = 5;
const int WOLF_TIMER = 10;
void printState(const vector<string> & board, const vector<vector<int>> & grass, const vector<vector<int>> & hunger) {
int rows = board.size();
int cols = board[0].size();
F(r, 0, rows) {
F(c, 0, cols) {
if (board[r][c] != '.')
cout << board[r][c];
else if (grass[r][c] < 0)
cout << "*";
else if (grass[r][c] >= GRASS_TIMER)
cout << "#";
else
cout << ".";
}
cout << endl;
}
}
void makeMove(vector<string> & board, vector<vector<int>> & grass, vector<vector<int>> & hunger) {
int rows = board.size();
int cols = board[0].size();
vector<string> newBoard(rows, string(cols, '.'));
auto newGrass = grass;
vector<vector<int>> newHunger(rows, vector<int>(cols));
// move sheep
F(r, 0, rows) {
F(c, 0, cols) {
if (board[r][c] == 'S') {
newHunger[(r+1)%rows][c] = hunger[r][c];
newBoard [(r+1)%rows][c] = 'S';
}
}
}
// move wolves
F(r, 0, rows) {
F(c, 0, cols) {
if (board[r][c] == 'W') {
newHunger[r][(c+1)%cols] = hunger[r][c];
if (newBoard[r][(c+1)%cols] == 'S') {
// eat sheep
newGrass [r][(c+1)%cols] = -1e9;
newHunger[r][(c+1)%cols] = -1;
}
newBoard[r][(c+1)%cols] = 'W';
}
}
}
// eat grass
F(r, 0, rows) {
F(c, 0, cols) {
if (newBoard[r][c] == 'S' && newGrass[r][c] >= GRASS_TIMER) {
newHunger[r][c] = -1;
newGrass [r][c] = -1;
}
}
}
// grow hunger
F(r, 0, rows) {
F(c, 0, cols) {
if (newBoard[r][c] == 'S' || newBoard[r][c] == 'W')
++newHunger[r][c];
}
}
// die of hunger
F(r, 0, rows) {
F(c, 0, cols) {
if (newBoard[r][c] == 'S' && newHunger[r][c] >= SHEEP_TIMER) {
newGrass [r][c] = -1e9;
newHunger[r][c] = 0;
newBoard [r][c] = '.';
}
if (newBoard[r][c] == 'W' && newHunger[r][c] >= WOLF_TIMER) {
newGrass [r][c] = -1e9;
newHunger[r][c] = 0;
newBoard [r][c] = '.';
}
}
}
// grow grass
F(r, 0, rows) {
F(c, 0, cols) {
++newGrass[r][c];
}
}
swap(board, newBoard);
swap(grass, newGrass);
swap(hunger, newHunger);
}
int main() {
int turns, rows, cols;
cin >> turns >> rows >> cols;
vector<string> board(rows);
for (auto & row : board)
cin >> row;
vector<vector<int>> grass (rows, vector<int>(cols));
vector<vector<int>> hunger(rows, vector<int>(cols));
while (turns--) {
// printState(board, grass, hunger);
// cout << endl;
makeMove(board, grass, hunger);
}
printState(board, grass, hunger);
cout << endl;
return 0;
}
没看
题意简述
给定一张由若干个点构成的图 G1,这些点全部按照某一向量移动得到图 G2,现在给出 G1 和 G2 混合后的图,请回答该图可能是由 G1 沿着多少种不同的非零向量移动得到。
输入 n 个点,每个点由(x,y)表示。
解题思路
如果一个向量是合法的,那么任意一个点按照该向量正向移动或反向移动一定都能与另一个点重合。
所有的向量最多只有 n 种可能,即可以从任意一个点计算与其它点之间的向量,然后再二分判断即可,总时间复杂度为 O ( N 2 ) O(N^2) O(N2)
代码示例
#include
using namespace std;
const int N = 1e3+10;
int n,ans = 0;
pair<int,int> pa[N];
set<pair<int,int> > st;
bool check(int x,int y){
for(int i = 1;i <= n;i++){
bool flag = false;
pair<int,int> tmp = make_pair(pa[i].first-x,pa[i].second-y);
flag |= st.find(tmp) != st.end();
tmp = make_pair(pa[i].first+x,pa[i].second+y);
flag |= st.find(tmp) != st.end();
if(!flag) return false;
}
return true;
}
void solve(){
sort(pa+1,pa+1+n);
for(int i = 1;i <= n;i++) st.insert(pa[i]);
for(int i = 2;i <= n;i++){
int x = pa[i].first-pa[1].first;
int y = pa[i].second-pa[1].second;
if(check(x,y)) ans += 2;
}
printf("%d\n",ans);
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++)
scanf("%d%d",&pa[i].first,&pa[i].second);
solve();
return 0;
}
在此之前我一直认为我的英语阅读水平还可以,至少高于平均,但是我错了,这次比赛我读的太艰难了。
这次一共 10 题,自己做能完成 6 题,其中 C 是求平面中矩形和圆形相交的面积,计算几何不太熟,没思路;H 是模拟,我写了很久但老有几个点过不了;I 没看。
总体看来这套题目不是很难,但是许多题题意理解很容易出偏差,导致前功尽弃,而且如果不是因为有测试数据,我也很难在短时间内写正确,所以还是需要练习。