CTU Open Contest 2019补题

本次比赛题目按照通过人数升序依次为:A B F C G J H E I D。
测试地址:计蒜客
官方数据、题面、题解

文章目录

        • A. Beer Barrels
        • B. Beer Bill
        • C. Beer Coasters
        • D. Beer Flood
        • E. Beer Game
        • F. Beer Marathon
        • G. Beer Mugs
        • H. Screamers in the Storm
        • I. Sixpack
        • J. Beer Vision
        • 总结

A. Beer Barrels

题意简述
给出四个整数: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} 2k1 个,第二位等于 C 的共 2 k − 1 2^{k-1} 2k1 个,同理第三位,第四位,… ,第 k 位等于 C 的都是 2 k − 1 2^{k-1} 2k1 个,故最终答案为 k ∗ 2 k − 1 k * 2^{k-1} k2k1

代码示例

#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;
}

B. Beer Bill

题意简述
计算字符串的价格。给多个字符串,每个串占一行。字符串分两种,一种字符串名为 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;
}

C. Beer Coasters

题意简述
给定一个圆的圆心(x,y)和半径 r,再给出一个矩形的两点坐标,试求出该矩形与圆相交的面积。

解题思路
用圆和矩形相交面积的模板来求。

D. Beer Flood

题意简述
给定一个无自环、无重边的有向图,要求在保证“从源点可以到达任意点,从任意点可以到达汇点”的情况下,最多能删掉几条多余的边。 其中,源点是图中入度为零的点,汇点是图中出度为零的点。

解题思路
二分图问题。

代码示例

E. Beer Game

题意简述
给定两个字符串,字符串仅包含数字和小写字母。可以进行如下操作:

  1. 插入:可以向一个字符串中插入一个字符。
  2. 兑换:如果某一位是数字 x,那么这 x 可以兑换为任意 x 个小写字母(必须兑换!)
  3. 删除:可以选定某一位并删除该位置上的小写字母。

输出最小的操作次数,可以使得这两个字符串相等。

解题思路
对比普通的“编辑距离”问题,这题唯一的区别就在于如果遇到数字,则必须将它替换为任意的小写字母,也就是说有几个数字首先就需要进行几次操作。我们可以先预处理,使得数字全替换为对应长度的 ‘#’ ,代表通配符,这样就可以转化为“编辑距离”问题了。
需要注意的是如果有连续的数字,并不是将其看作十进制整体,而是全看作单独的数字。

设 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;
}

F. Beer Marathon

题意简述
一维坐标轴上有 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;
}

G. Beer Mugs

题意简述
给定一个字符串 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;
}

H. Screamers in the Storm

题意简述
在一个 m * n 的矩阵上,每个方块由不同元素构成,有如下规则:

  1. 每回合狼只能向右走,走不通就向左走。羊只能向下走,走不通就向上走。
  2. 如果狼和羊在同一个方格,那么狼吃羊,此方格被标记为‘墓地’。
  3. 如果羊在草地上,那么羊吃草,此方格变为‘光秃秃’。
  4. 狼在 10 回合之内没吃到羊就会死,它死的方格变为‘墓地’,羊在 5 回合没吃到草就会死,它死的方格变为‘墓地’。

而不同方块之间的转化规则为:

  1. ‘光秃秃’在三回合后会变成‘草地’
  2. ‘草地’被羊吃了会变成‘光秃秃’
  3. ‘墓地’可以被经过,但永远不会长草。

给出初始图,输出 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;
}

I. Sixpack

没看

J. Beer Vision

题意简述
给定一张由若干个点构成的图 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 没看。
总体看来这套题目不是很难,但是许多题题意理解很容易出偏差,导致前功尽弃,而且如果不是因为有测试数据,我也很难在短时间内写正确,所以还是需要练习。

你可能感兴趣的:(ACM学习笔记专栏,假期练习)