EOJ 杂题合集
目录
整理了 EOJ 中遇到的一些值得一记的题目。
EOJ1424 Hard to Believe, but True! #
给定一个等式,问等式从右往左读是否正确。
题目不难,不过用到了一些有趣的字符串处理,所以记录下来。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void rev_str(char s[]){
int c,i,j, len = strlen(s);
for (i = 0, j = len - 1; i < j; i++, j--){
c = s [i];
s[i] = s[j];
s[j] = c;
}
}
int main()
{
char str1[8]={0},str2[8]={0},str3[8]={0};
while (~scanf("%[^+]+%[^=]=%s", str1, str2, str3)){
rev_str(str1);rev_str(str2);rev_str(str3);
if (atoi(str3) == atoi(str2) + atoi(str1))
printf("True\n");
else printf("False\n");
if (atoi(str3) == 0 && atoi(str2) == 0 && atoi(str1) == 0) break;
}
return 0;
}
EOJ3322 万年历 #
给定一个日期,输出该日期是星期几。
这是个历史题,就是所谓 OEIS 题的同类。
为什么说是历史题? #
罗马教皇格里高利十三世在 1582 年组织了一批天文学家,根据哥白尼日心说计算出来的数据,对儒略历作了修改。将 1582 年 10 月 5 日到 14 日之间的 10 天宣布撤销,继 10 月 4 日之后为 10 月 15 日。后来人们将这一新的历法称为 “格里高利历”,也就是今天世界上所通用的历法,简称格里历或公历。
所以怎么算星期几? #
蔡勒公式 (Zeller formula):
1582.10.4 后:
$$ w=(y+[\frac{y}{4}]+[\frac{c}{4}]-2c+[\frac{13(m+1)}{5}]+d-1)%7
$$
1582.10.4 及之前:
$$ w=(y+[\frac{y}{4}]+[\frac{c}{4}]-2c+[\frac{13(m+1)}{5}]+d+2)%7 $$
其中 m, d 对应月日,c 为年份前两位(世纪数 ** 减 1**),y 为年份后两位。
基姆拉尔森公式 (Kim larsson calculation formula):
$$ w=(d+2m+[\frac{3(m+1)}{5}]+y+[\frac{y}{4}]-[\frac{y}{100}]+[\frac{y}{400}]+1)%7 $$
需要注意的是,基姆拉尔森公式的结果为 0 时表示周一,6 表示周日,以此类推。
注意事项 #
- 两个公式都需要把每年的 1,2 两月看作上一年的 13,14 两月。
- 另有一种需要事先计算年月基数表的计算方法,由于较繁琐这里不再赘述。
代码 #
(仅以蔡勒公式为例)
#include <stdio.h>
void calc_day(int y, int m, int d)
{
if (m == 1 || m == 2){ // 1 月 2 月情况特判
--y;
m += 12;
}
int c = y / 100; // 取年份前两位
int yy = y - c * 100; // 取年份后两位
int day = yy + yy / 4 + c / 4 - 2 * c + 13 * (m + 1) / 5 + d - 1; // 蔡勒公式
if (y <= 1582 && m <= 10 && d <= 4) day += 3; // 判断是否在 1582 年 10 月 4 日前
while (day < 0) day += 7; // 结果可能为负,这里避免了容易出错的负数取模运算
day %= 7;
switch(day){
case 1: printf("Monday\n");break;
case 2: printf("Tuesday\n");break;
case 3: printf("Wednesday\n");break;
case 4: printf("Thursday\n");break;
case 5: printf("Friday\n");break;
case 6: printf("Saturday\n");break;
default: printf("Sunday\n");
}
return;
}
int main(void)
{
int y, m, d;
scanf("%d-%d-%d", &y, &m, &d);
calc_day(y, m, d);
return 0;
}
EOJ1224 简单迷宫问题 #
一天,sunny 不小心进入了一个迷宫,不仅很难寻找出路,而且有的地方还有怪物,但是 sunny 有足够的能力杀死怪物,但是需要一定的时间,但是 sunny 想早一点走出迷宫,所以请你帮助他计算出最少的时间走出迷宫,输出这个最少时间。 我们规定每走一格需要时间单位 1, 杀死怪物也需要时间 1, 如果不能走到出口,则输出 impossible. 每次走只能是上下左右 4 个方向。
先四面造墙,省去判断是否出界的函数。之后对于有怪物的格子,新状态再搜索树中是当前状态的下两层的,因此不能走过之后直接 step+2
标记掉,而是要记录下这个状态 (cnt=1
)。很容易 WA 的点。
#include <cstdio>
#include <queue>
using namespace std;
#define NEXT mp[p.x+dx[i]][p.y+dy[i]]
struct node{int x, y, step, cnt;};
char mp[202][202];
int sx, sy, n, m, i;
const int dx[4] = {-1, 0, 1, 0};
const int dy[4] = {0, 1, 0, -1};
void bfs()
{
queue<node> q;
q.push({sx, sy, 0, 0});
mp[sx][sy] = '#';
while (!q.empty()){
node p = q.front();
q.pop();
if (p.cnt == 1){
p.cnt = 0;
q.push(p);
continue;
}
for (i = 0; i < 4; ++i)
switch(NEXT){
case '.': NEXT = '#'; q.push({p.x+dx[i], p.y+dy[i], p.step+1, 0}); break;
case 'X': NEXT = '#'; q.push({p.x+dx[i], p.y+dy[i], p.step+2, 1}); break;
case 'T': printf("%d\n", p.step+1); return;
}
}
printf("impossible\n");
}
void init()
{
sx = sy = 0;
for (i = 1; i <= n; ++i){
for (int j = 1; j <= m; ++j){
scanf("%c", &mp[i][j]);
if (mp[i][j] == 'S') sx = i, sy = j;
}
getchar();
}
for (i = 0; i <= m+1; ++i)
mp[0][i] = mp[n+1][i] = '#';
for (i = 0; i <= n+1; ++i)
mp[i][0] = mp[i][m+1] = '#';
}
int main()
{
while (~scanf("%d%d\n", &n, &m)){
init();
bfs();
}
return 0;
}
EOJ3367 咸鱼翻身 #
给定 01 序列,选择一个区间,对区间中每个数取反(0 变 1,1 变 0),求 1 最多能有多少个。
最大区间和问题。对于 1,翻转后收益(1 的个数)为 -1;对于 0,翻转后收益为 +1。方便起见,读入时直接把数字转换为收益。最后求最大区间和,加上原来 1 的个数就是答案。由于和最大的区间中不会有 -1 出现,区间所覆盖的位置也不会包括原来 1 所在的位置,因此不会重复。
#include <stdio.h>
int a[100001];
int main()
{
int n,i,j,sum = 0,tot = 0,ans;
scanf("%d", &n);
for (i = 0; i < n; ++i){
scanf("%d", &j);
if (j){
a[i] = -1;
sum++;
}
else a[i] = 1;
}
ans = a[0];
for (i = 0; i < n; ++i){
if (tot> 0) tot += a[i];
else tot = a[i];
ans = (tot>ans)?tot:ans;
}
printf("%d\n", ans + sum);
return 0;
}
EOJ3388 Balanced Lineup #
给定一个数列,求询问区间中的最大最小值之差。
普通方法用线段树或 ST 算法,对这题来说区间 RMQ 就可以了。时间复杂度 O(nlogn)
。
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int linemax[50001][20], linemin[50001][20];
void rmq(int n)
{
for (int j = 1; j < 20; ++j)
for (int i = 1; i <= n; ++i)
if (i + (1 << j) - 1 <= n){
linemax[i][j] = max(linemax[i][j - 1], linemax[i + (1 << (j - 1))][j - 1]);
linemin[i][j] = min(linemin[i][j - 1], linemin[i + (1 << (j - 1))][j - 1]);
}
}
int main(void)
{
int n, len, a, b, x, maxc, minc;
scanf("%d%d", &n, &len);
for (int i = 1; i <= n; ++i){
scanf("%d", &linemax[i][0]);
linemin[i][0] = linemax[i][0];
}
rmq(n);
while(len--){
scanf("%d%d", &a, &b);
x = (int)(log(b - a + 1) / log(2));
maxc = max(linemax[a][x], linemax[b - (1 << x) + 1][x]);
minc = min(linemin[a][x], linemin[b - (1 << x) + 1][x]);
printf("%d\n", maxc - minc);
}
return 0;
}
EOJ1499 矩阵快速幂求斐波那契数列 #
给定 n,求斐波那契数列前 n 项和。
0 < n < 1e9
快速幂实现方式和整数差不多,没什么好讲的。而对于斐波那契数列,不难发现:
$$ \begin{pmatrix} F_{n+2} & a\cr F_{n+1} & b \end{pmatrix}{=} \begin{pmatrix} 1 & 1\cr 1 & 0 \end{pmatrix} \begin{pmatrix} F*{n+1}\cr F_n \end{pmatrix} $$
我们记
$$ \begin{pmatrix} 1 & 1\cr 1 & 0 \end{pmatrix} $$
为 $A$,由此我们可以推得:
$$ \begin{pmatrix} F*{n+1}\cr F_n \end{pmatrix}{=} A^n \begin{pmatrix} 1\cr 0 \end{pmatrix} $$
于是对 $A^n$ 求解就很容易得到答案了。
#include <iostream>
#define MOD 100000000
using namespace std;
typedef long long ll;
struct Mat{
ll m[2][2];
};
Mat MatMul(Mat A, Mat B)
{
Mat ret;
for (int i = 0; i < 2; ++i)
for (int j = 0 ; j < 2; ++j){
ret.m[i][j] = 0;
for (int k = 0; k < 2; ++k)
ret.m[i][j] += A.m[i][k] * B.m[k][j] % MOD;
}
return ret;
}
Mat MatPow(Mat A, ll n)
{
Mat ret;
ret.m[0][0]=1;
ret.m[0][1]=0;
ret.m[1][0]=0;
ret.m[1][1]=1;
while (n){
if (n & 1)
ret = MatMul(ret, A);
A = MatMul(A, A);
n >>= 1;
}
return ret;
}
int main()
{
ll n;
while(cin>> n){
Mat ans, A;
ans.m[0][0]=1;
ans.m[0][1]=0;
A.m[0][0]=1;
A.m[0][1]=1;
A.m[1][0]=1;
A.m[1][1]=0;
ans = MatMul(ans, MatPow(A, n + 1));
cout <<ans.m[0][0] - 1 << endl;
}
return 0;
}
EOJ3006 计算多项式的系数 #
给定一个多项式 $(ax+by)^k$,计算多项式展开后 $x^ny^m$ 项的系数,结果对 1000000007 取模 0≤k≤1,000,000,0≤n,m≤k,且 n+m=k,0≤a,b≤10^9。
由于 n, m 过大不能直接预处理组合数;又因为 p 过大 Lucas 定理的递归形式在这里也不能直接使用。后来才发现原来只要预处理阶乘 + 快速幂 + 逆元求组合数就可以了。
#include <bits/stdc++.h>
#define p 1000000007
#define K 1000000
typedef long long ll;
ll a, b, k, n, m, fac[K+1];
void init()
{
fac[0] = 1;
for(int i = 1; i <= K; ++i)
fac[i] = fac[i-1]*i % p;
}
ll pow_mod(ll a, ll x)
{
ll ret = 1;
while (x) {
if (x & 1) ret = (ret * a) % p;
a = (a * a) % p;
x >>= 1;
}
return ret;
}
ll C(ll n, ll m)
{
if(m> n) return 0;
return fac[n] * pow_mod((fac[m]*fac[n-m])%p, p-2) % p;
}
int main()
{
int cas;
ll ans;
scanf("%d", &cas);
init();
for (int t = 0; t < cas; ++t) {
scanf("%lld%lld%lld%lld%lld", &a, &b, &k, &n, &m);
ans = pow_mod(a, n);
ans = (ans * pow_mod(b, m)) % p;
ans = (ans * C(k, m)) % p;
printf("case #%d:\n%lld\n", t, ans);
}
return 0;
}
顺便放个 Lucas 模板备用,n, m 较大而 p 较小时可用:
#include<bits/stdc++.h>
#define P 10007
using namespace std;
typedef long long ll;
ll pow_mod(ll a, ll x, int p)
{
ll ret = 1;
while(x) {
if (x & 1) ret = ret * a % p;
a = a * a % p;
x >>= 1;
}
return ret;
}
ll comb(ll a, ll b, int p)
{
if (a < b) return 0;
if (a == b) return 1;
if (b> a - b) b = a - b;
ll ans = 1, ca = 1, cb = 1;
for (ll i = 0; i < b; ++i) {
ca = (ca * (a - i)) % p;
cb = (cb * (b - i)) % p;
}
ans = (ca * pow_mod(cb, p-2, p)) % p;
return ans;
}
ll lucas(ll n, ll m, int p)
{
ll ans = 1;
while(n && m && ans) {
ans = (ans * comb(n%p, m%p, p)) % p;
n /= p;
m /= p;
}
return ans;
}
int main()
{
int cas, k;
ll a, b, n, m;
cin >> cas;
for (int t = 0; t < cas; ++t) {
cin >> a >> b >> k >> n >> m;
printf("case #%d:\n", t);
if (n + m != k) cout <<"0" << endl;
else {
ll ans = pow_mod(a, n, P);
ans = (ans * pow_mod(b, m, P)) % P;
ans = (ans * lucas(k, n, P)) % P;
cout << ans << endl;
}
}
return 0;
}
EOJ3458 Cards Game #
每次从 N 张牌中选择两张,代价为
min(r[i]^b[j], r[j]^b[i])
, 然后从两张中选择一张删去进入下一轮,循环直至只剩一张牌,求最小代价和。
来自 Google Kickstart Round G 2017 的 B 题。MST 的奇怪用法。
可以发现有 N 张牌,N-1 次操作。在扔掉的牌与留下的牌之间连边,则边的权值 == 题目中的代价。这样求最小代价和就转化成求图的最小生成树问题,Prim 或者 Kruskal(如下代码)直接过。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 101;
int r[N], b[N];
int fa[N];
int union_find(int x)
{
return fa[x] == x ? x : fa[x] = union_find(fa[x]);
}
bool unite(int a, int b)
{
a = union_find(a);
b = union_find(b);
if (a != b) {
fa[a] = b;
return 1;
}
return 0;
}
vector<tuple<int, int, int> > v;
ll kruskal()
{
sort(v.begin(), v.end());
ll ans = 0;
for (auto i : v) {
int u, v, w;
tie(w, u, v) = i;
if (unite(u, v))
ans += w;
}
return ans;
}
int main()
{
int cas, n, i;
cin >> cas;
for (int t = 1; t <= cas; ++t) {
cin >> n;
v.clear();
for (i = 0; i < n; ++i) cin >> r[i];
for (i = 0; i < n; ++i) cin >> b[i];
for (i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j)
v.emplace_back(min(r[i]^b[j], r[j]^b[i]), i, j);
fa[i] = i;
}
printf("Case #%d: %lld\n", t, kruskal());
}
return 0;
}
EOJ2069 Asteroids #
N×N 网格中有若干个小行星,武器每次发射可以清除一行或一列,问最少需要发射多少次才能清除全部小行星。
所有小行星横坐标为一个点集,纵坐标为另一个点集。对于每个小行星,在其横坐标与纵坐标之间连一条边,则问题转化为求二分图最小点覆盖。又因为二分图最小点覆盖 == 二分图最大匹配,所以直接跑匈牙利就行。
#include <bits/stdc++.h>
using namespace std;
bool path[505][505], v[505];
int ast[505], n, k;
bool dfs(int x)
{
for (int i = 1; i <= n; ++i)
if (!v[i] && path[x][i]) {
v[i] = 1;
if (!ast[i] || dfs(ast[i])) {
ast[i] = x;
return 1;
}
}
return 0;
}
int main()
{
int x, y, ans = 0;
cin >> n >> k;
while (k--) {
scanf("%d%d", &x, &y);
path[x][y] = 1;
}
for (int i = 1; i <= n; ++i) {
memset(v, 0, sizeof(v));
if (dfs(i)) ++ans;
}
printf("%d\n", ans);
return 0;
}
EOJ3384 食物链 #
动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。 现有 N 个动物,以 1-N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。 有人用两种说法对这 N 个动物所构成的食物链关系进行描述: 第一种说法是 1 X Y,表示 X 和 Y 是同类。 第二种说法是 2 X Y,表示 X 吃 Y。 此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话: 当前的话与前面的某些真的话冲突,就是假话; 当前的话中 X 或 Y 比 N 大,就是假话; 当前的话表示 X 吃 X,就是假话。 你的任务是根据给定的 N (1≤N≤50 000) 和 K 句话 (0≤K≤100 000),输出假话的总数。
经典并查集(对我来说还是太难了)。代码中 kind
数组为 0 表示与父节点同类,1 表示被父节点吃,2 表示吃父节点。
初始化时,每个节点自身成为一个集合,并且父节点也是自身(0:同类)。路径压缩的时候可以顺便更新掉 kind
数组的状态。
Union
函数中:
- 如果
x,y
不在一个集合中,关于合并之后kind
要怎么变化,这个式子我纯粹靠找规律写了,可能网上有更靠谱的解法。 - 如果
x,y
同属一个集合,那么只需要验证这句话是不是对的就可以了。
#include <iostream>
using namespace std;
int father[50005], kind[50005];
int Find(int x)
{
if (x == father[x]) return father[x];
int y = Find(father[x]);
kind[x] = (kind[x] + kind[father[x]]) % 3;
return father[x] = y;
}
int Union(int op, int sp1, int sp2)
{
int x = Find(sp1), y = Find(sp2);
if (x == y){
if ((kind[sp1] - kind[sp2] + 3) % 3 == op - 1) return 0;
return 1;
}
father[x] = y;
kind[x] = (-kind[sp1] + op - 1 + kind[sp2] + 3) % 3;
return 0;
}
int main()
{
int n, k, op, sp1, sp2, cnt = 0, i;
cin >> n >> k;
for (i = 1; i <= n; ++i){
father[i] = i;
kind[i] = 0;
}
for (i = 0;i < k; ++i){
cin >> op >> sp1 >> sp2;
if (op == 2 && sp1 == sp2) ++cnt;
else if (sp1> n || sp2 > n) ++cnt;
else cnt += Union(op, sp1, sp2);
}
cout << cnt << endl;
return 0;
}