比赛地址
弱校连萌寒假专题一
A. Xiangqi
题意:
给定一个局面,问这个局面下红方是否可以将军。局面上黑方只有一将,红方可以有帅、车、炮、马,将和帅被限制在田字格里。黑方先行。规则同象棋。
题解:
枚举黑方决策,再枚举红方决策,检查对于每一种黑方决策是否都至少存在一种红方决策使得黑方被红方将军。
代码:
#include <cstdio>
#include <cstring>
const int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
char g[11][11];
bool checkmate(int x, int y)
{
for(int i = 0; i < 4; ++i)
{
if(dx[i])
{
if((!g[x + dx[i]][y - 1] && g[x + dx[i] + dx[i]][y - 1] == 'H')
|| (!g[x + dx[i]][y + 1] && g[x + dx[i] + dx[i]][y + 1] == 'H'))
return 1;
}
else
{
if((!g[x - 1][y + dy[i]] && g[x - 1][y + dy[i] + dy[i]] == 'H')
|| (!g[x + 1][y + dy[i]] && g[x + 1][y + dy[i] + dy[i]] == 'H'))
return 1;
}
bool flag = 0;
int xx = x + dx[i], yy = y + dy[i];
while(xx > 0 && xx <= 10 && yy > 0 && yy <= 9)
{
if(!flag)
{
if(g[xx][yy] == 'R' || g[xx][yy] == 'G')
return 1;
}
else
{
if(g[xx][yy] == 'C')
return 1;
}
if(g[xx][yy])
{
if(!flag)
flag = 1;
else
break;
}
xx += dx[i];
yy += dy[i];
}
}
return 0;
}
int main()
{
int n, x, y, xx, yy;
char op[2];
while(scanf("%d%d%d", &n, &x, &y) == 3 && n && x && y)
{
memset(g, 0, sizeof g);
while(n--)
{
scanf("%s%d%d", op, &xx, &yy);
g[xx][yy] = op[0];
}
bool flag = 1;
for(int i = 0; i < 4; ++i)
{
xx = x + dx[i];
yy = y + dy[i];
if(xx < 1 || xx > 3 || yy < 4 || yy > 6)
continue;
if(!checkmate(xx, yy))
{
flag = 0;
break;
}
}
puts(flag ? "YES" : "NO");
}
return 0;
}
B. Squares
题意:
给定n * n的点阵,m条边,每条边是水平或垂直地连接两个相邻的点,问点阵里构成多少个长度为多少的正方形。
2 <= n <= 9, 0 <= m <= 2 * (n - 1) * n。
题解:
代码的算法是算出行与列的邻接矩阵,枚举边长和左上角的点,检查正方形四个角是否连通,时间复杂度O(n^3)。
如果不用考虑分别输出每个边长的答案,则有O(n^2logn)的算法,对应题目为poj2445,注意输入和输出都和本题略有差别。
首先枚举每个点,计算出它向四个方向所能扩展的最大长度,时间复杂度可以做到O(n^2)。
然后考虑每条i - j值相等的斜线上的点,正方形的左上右下角一定在同一条斜线上,可以用一颗线段树维护向下向右的部分,查询向上向左的部分,时间复杂度O(n^2logn)。
线段树的使用方法有很多,这里可以是单点修改、全局减一和区间查询正数个数的线段树,每个叶子只需要维护元素值,每个区间只需要维护正数的个数即可。
代码:
#include <cstdio>
#include <cstring>
const int maxn = 10;
int t, n, m, ans[maxn];
bool h[maxn][maxn][maxn], v[maxn][maxn][maxn];
int main()
{
for(int Case = 1; ; ++Case)
{
memset(ans, 0, sizeof ans);
memset(h, 0, sizeof h);
memset(v, 0, sizeof v);
if(scanf("%d%d", &n, &m) != 2)
break;
for(int i = 0; i < m; ++i)
{
char op[2];
int x, y;
scanf("%s%d%d", op, &x, &y);
--x, --y;
if(op[0] == 'H')
h[x][y][y + 1] = 1;
else
v[x][y][y + 1] = 1;
}
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
for(int k = j + 2; k < n; ++k)
{
h[i][j][k] = h[i][j][k - 1] & h[i][k - 1][k];
v[i][j][k] = v[i][j][k - 1] & v[i][k - 1][k];
}
if(Case > 1)
puts("\n**********************************\n");
printf("Problem #%d\n\n", Case);
bool flag = 0;
for(int k = 1; k < n; ++k)
{
int ans = 0;
for(int i = 0; i + k < n; ++i)
for(int j = 0; j + k < n; ++j)
if(h[i][j][j + k] && h[i + k][j][j + k] && v[j][i][i + k] && v[j + k][i][i + k])
++ans;
if(ans)
{
printf("%d square (s) of size %d\n", ans, k);
flag = 1;
}
}
if(!flag)
puts("No completed squares can be found.");
}
return 0;
}
C. Othello
题意:
给定一个8 * 8的黑白棋局面和起始方,维护一些操作:下棋并输出黑白棋个数,输出当前可行解,结束游戏并输出局面。
其中下棋操作如果对于当前方非法,则由对战方下此棋,数据保证合法,下一次下棋的对象是当前方。
题解:
维护局面的黑白棋个数,对于下棋操作,dfs找到需要覆盖的部分进行覆盖,对于可行解部分,枚举每个未盖点,dfs检验它是否合法。
代码:
#include <cstdio>
#include <cstring>
const char* out = "BW-";
const int dx[] = {-1, 0, 1, -1, 1, -1, 0, 1}, dy[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int t, chess[8][8], now, cnt[2];
bool rush, vis[8][8];
char str[10];
void fresh()
{
memset(vis, 0, sizeof vis);
for(int i = 0; i < 8; ++i)
for(int j = 0; j < 8; ++j)
if(chess[i][j] == 2)
for(int k = 0; k < 8; ++k)
{
int ii = i + dx[k], jj = j + dy[k];
if(ii < 0 || ii >= 8 || jj < 0 || jj >= 8 || chess[ii][jj] != (now ^ 1))
continue;
for(ii += dx[k], jj += dy[k]; ii >= 0 && ii < 8 && jj >= 0 && jj < 8; ii += dx[k], jj += dy[k])
if(chess[ii][jj] == now)
{
vis[i][j] = 1;
break;
}
else if(chess[ii][jj] != (now ^ 1))
break;
if(vis[i][j])
break;
}
rush = 1;
}
void cover(int x, int y)
{
for(int i = 0; i < 8; ++i)
{
int xx = x + dx[i], yy = y + dy[i];
if(xx < 0 || xx >= 8 || yy < 0 || yy >= 8 || chess[xx][yy] != (now ^ 1))
continue;
bool flag = 0;
for(xx += dx[i], yy += dy[i]; xx >= 0 && xx < 8 && yy >= 0 && yy < 8; xx += dx[i], yy += dy[i])
if(chess[xx][yy] == now)
{
flag = 1;
break;
}
else if(chess[xx][yy] != (now ^ 1))
break;
if(flag)
for(xx = x + dx[i], yy = y + dy[i]; xx >= 0 && xx < 8 && yy >= 0 && yy < 8; xx += dx[i], yy += dy[i])
if(chess[xx][yy] == now)
break;
else
{
chess[xx][yy] = now;
++cnt[now];
--cnt[now ^ 1];
}
}
}
int main()
{
scanf("%d", &t);
while(t--)
{
rush = cnt[0] = cnt[1] = 0;
for(int i = 0; i < 8; ++i)
{
scanf("%s", str);
for(int j = 0; j < 8; ++j)
if(str[j] == 'B')
{
chess[i][j] = 0;
++cnt[0];
}
else if(str[j] == 'W')
{
chess[i][j] = 1;
++cnt[1];
}
else
chess[i][j] = 2;
}
scanf("%s", str);
now = str[0] == 'W';
while(scanf("%s", str) != EOF && str[0] != 'Q')
if(str[0] == 'L')
{
if(!rush)
fresh();
bool flag = 0;
for(int i = 0; i < 8; ++i)
for(int j = 0; j < 8; ++j)
if(vis[i][j])
{
if(flag)
putchar(' ');
else
flag = 1;
printf("(%d,%d)", i + 1, j + 1);
}
if(!flag)
printf("No legal move.");
putchar('\n');
}
else
{
int x = str[1] - '1', y = str[2] - '1';
if(!rush)
fresh();
if(!vis[x][y])
now ^= 1;
chess[x][y] = now;
cover(x, y);
++cnt[now];
rush = 0;
now ^= 1;
printf("Black - %d White - %d\n", cnt[0], cnt[1]);
}
for(int i = 0; i < 8; ++i)
{
for(int j = 0; j < 8; ++j)
str[j] = out[chess[i][j]];
str[8] = '\0';
puts(str);
}
putchar('\n');
}
return 0;
}
D. IP Networks
题意:
给定局域网内的n个ip地址,求这个局域网的地址和子网掩码。规则和ip地址与子网掩码的定义相同。
n <= 1000。
题解:
按照定义计算即可。时间复杂度O(n)。
代码:
#include <cstdio>
const int maxm = 1001;
int m, ip[maxm][4], maxl, addr[4], mask[4];
int min(int x, int y)
{
return x < y ? x : y;
}
int max(int x, int y)
{
return x < y ? y : x;
}
int len(int x)
{
int ret = 0;
for( ; x; ++ret, x >>= 1);
return ret;
}
int main()
{
while(scanf("%d", &m) == 1)
{
for(int i = 0; i < 4; ++i)
addr[i] = mask[i] = 255;
for(int i = 0; i < m; ++i)
for(int j = 0; j < 4; ++j)
{
scanf("%*c%d", &ip[i][j]);
addr[j] &= ip[i][j];
}
maxl = 0;
for(int i = 0; i < m; ++i)
for(int j = 0; j < 4; ++j)
if(addr[j] ^ ip[i][j])
maxl = max(maxl, len(addr[j] ^ ip[i][j]) + (3 - j << 3));
for(int j = 3; j >= 0; --j)
if(maxl > 0)
{
mask[j] &= ~((1 << min(maxl, 8)) - 1);
maxl -= 8;
}
for(int i = 0; i < 4; ++i)
printf("%d%c", addr[i] & mask[i], ".\n"[i + 1 == 4]);
for(int i = 0; i < 4; ++i)
printf("%d%c", mask[i], ".\n"[i + 1 == 4]);
}
return 0;
}
E. Flooded!
题意:
给定n * m块土地,每块土地有一定高度,面积为100 m^2,现在有k m^2的洪水,问洪水的水位有多高,洪水淹没的土地占总土地的比重是多少。
n, m <= 30, k is an integer.
题解:
将土地按高度排序,依次灌水即可。时间复杂度O(nmlognm)。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxk = 1000;
int n, m;
double a[maxk], x, sum, ans, cnt;
int main()
{
for(int Case = 1; scanf("%d%d", &n, &m) == 2; ++Case)
{
if(!n && !m)
break;
n *= m;
for(int i = 0; i < n; ++i)
scanf("%lf", &a[i]);
sort(a, a + n);
scanf("%lf", &x);
x /= 100;
sum = ans = cnt = 0;
bool flag = 0;
for(int i = 1; i < n; ++i)
{
double tmp = (a[i] - a[i - 1]) * i;
if(tmp + sum > x)
{
ans = (x - sum) / i + a[i - 1];
cnt = i * 100.0 / n;
break;
}
else
sum += tmp;
}
if(!cnt)
{
ans = (x - sum) / n + a[n - 1];
cnt = 100.0;
}
printf("Region %d\n", Case);
printf("Water level is %.2f meters.\n", ans);
printf("%.2f percent of the region is under water.\n\n", cnt);
}
return 0;
}
F. Morse Mismatches
题意:
给定某些字符的摩斯电码,给定一个字典,请你识别一些密码,找出与密码差距最小的摩斯电码对应的字典序最小的单词word,非精确匹配输出word?,单一匹配输出word,多匹配输出word!。两个字符串差距的大小定义为字符串的对应位有多少位不同。
字符串长度 <= 80, 字典大小 <= 100。
题解:
对于每个字典里的单词,算出加密之后的摩斯电码,对于每个密码进行匹配。
设单词数为n,密码数为m,时间复杂度O(nm)。
代码:
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
string tmp1, tmp2, dict[256], word[101], tran[101];
int diff(string a, string b)
{
if(a == b)
return 0;
if(a.size() < b.size())
swap(a, b);
if(a.substr(0, b.size()) == b)
return a.size() - b.size();
return INF;
}
int main()
{
ios::sync_with_stdio(0);
while(cin >> tmp1 && tmp1[0] != '*')
{
cin >> tmp2;
dict[tmp1[0]] = tmp2;
}
while(cin >> word[n] && word[n][0] != '*')
++n;
sort(word, word + n);
for(int i = 0; i < n; ++i)
for(int j = 0, k = (int)word[i].size(); j < k; ++j)
tran[i] += dict[word[i][j]];
while(cin >> tmp1 && tmp1[0] != '*')
{
int dis = INF, pos = 0, flag = 0;
for(int i = 0; i < n; ++i)
{
int tmp = diff(tmp1, tran[i]);
if(tmp < dis)
{
dis = tmp;
pos = i;
}
else if(!tmp && !dis)
flag = 1;
}
cout << word[pos] + (dis ? "?" : (flag ? "!" : "")) << endl;
}
return 0;
}
G. Repeating Decimals
题意:
给定一个分数,求它的循环小数表示,超过50位的用"..."表示。
分子, 分母 <= 3000。
题解:
小数位出现循环当且仅当到某一位的余数表示出现重复,不同的余数最多3000个,直接枚举小数位上每一位,并记录已经出现过的余数。
设分母为n,时间复杂度O(n)。
代码:
#include <cstdio>
#include <cstring>
int a, b, n, seq[3001], hash[3001];
int main()
{
while(scanf("%d%d", &a, &b) == 2)
{
memset(hash, -1, sizeof hash);
n = 0;
printf("%d/%d = %d.", a, b, a / b);
a %= b;
hash[a] = n;
while(1)
{
a *= 10;
seq[++n] = a / b;
if(hash[a %= b] == -1)
hash[a] = n;
else
break;
}
for(int i = 1; i <= hash[a]; ++i)
putchar('0' + seq[i]);
putchar('(');
for(int i = 1; i <= 50 && hash[a] + i <= n; ++i)
putchar('0' + seq[hash[a] + i]);
if(hash[a] + 50 < n)
printf("...");
printf(")\n %d = number of digits in repeating cycle\n\n", n - hash[a]);
}
return 0;
}
H. Kickdown
题意:
有宽度为2的板子,中间有地方被去除,问两个板子放入一个宽度为3的格挡里最短的长度为多少。
题解:
设两个版子的长度分别为n和m,枚举一个板子与另一个板子端点的相对位置,枚举后面每一位是否能合理的嵌入,时间复杂度O(nm)。
代码:
#include <cstdio>
#include <cstring>
int slen, tlen, ans;
char s[101], t[101];
int main()
{
while(scanf("%s%s", s, t) == 2)
{
slen = strlen(s);
tlen = strlen(t);
ans = slen + tlen;
for(int i = 0; i < slen; ++i)
{
bool flag = 0;
for(int j = 0; s[i + j] && t[j]; ++j)
if(s[i + j] == '2' && t[j] == '2')
{
flag = 1;
break;
}
if(!flag && i + tlen < ans)
ans = i + tlen;
}
for(int i = 0; i < tlen; ++i)
{
bool flag = 0;
for(int j = 0; t[i + j] && s[j]; ++j)
if(t[i + j] == '2' && s[j] == '2')
{
flag = 1;
break;
}
if(!flag && i + slen < ans)
ans = i + slen;
}
if(ans < slen)
ans = slen;
if(ans < tlen)
ans = tlen;
printf("%d\n", ans);
}
return 0;
}
I. DNA Consensus String
题意:
给定n个长度为m的DNA链样本,与他们最相似的DNA链每一位都是这n个DNA链上对应位出现次数最多的碱基,求最相似的链和不同的位的个数。
n <= 1000, m <= 50。
题解:
按每一位先算出所求DNA链,再统计差异数,时间复杂度O(nm)。
代码:
#include <cstdio>
#include <cstring>
int t, n, m, cnt[1001][4], ans, trans[256];
char str[1001], out[] = "ACGT";
int main()
{
trans['A'] = 0, trans['C'] = 1, trans['G'] = 2, trans['T'] = 3;
scanf("%d", &t);
while(t--)
{
memset(cnt, 0, sizeof cnt);
scanf("%d%d", &m, &n);
ans = n * m;
while(m--)
{
scanf("%s", str);
for(int i = 0; i < n; ++i)
++cnt[i][trans[str[i]]];
}
for(int i = 0; i < n; ++i)
{
int max = 0;
for(int j = 1; j < 4; ++j)
if(cnt[i][max] < cnt[i][j])
max = j;
ans -= cnt[i][max];
putchar(out[max]);
}
printf("\n%d\n", ans);
}
return 0;
}
J. Floating Point Numbers
题意:
给定一个IEEE float数的二进制表示,求它的科学表示法。
题解:
按照IEEE float数的定义直接计算即可,注意OJ上输出的指数只有两个数位,需要人为地加上一个前导0。
代码:
#include <cmath>
#include <cstdio>
int len;
double num, pow2[200];
char str[20];
bool flag;
int main()
{
pow2[100] = 1.0;
for(int i = 1; i <= 64; ++i)
{
pow2[100 + i] = pow2[100 + i - 1] * 2;
pow2[100 - i] = pow2[100 - i + 1] / 2;
}
puts("Program 6 by team X");
while(scanf("%s", str) != EOF)
{
len = 0;
for(int i = 1; i < 8; ++i)
len = (len << 1) + (str[i] == '1');
len -= 63;
num = 1.0;
for(int i = 1; i <= 8; ++i)
if(str[7 + i] == '1')
num += pow2[100 - i];
num *= pow2[100 + len];
flag = 0;
for(int i = 1; i < 16; ++i)
flag |= str[i] == '1';
if(str[0] == '1')
num = -num;
if(!flag)
num = 0;
sprintf(str, "%le", num);
if (num >= 0)
putchar(' ');
int i = 0;
while (str[i] != 'e')
putchar(str[i++]);
putchar(str[i++]);
putchar(str[i++]);
putchar('0');
while (str[i] != '\0')
putchar(str[i++]);
putchar('\n');
}
puts("End of program 6 by team X");
return 0;
}