比赛链接
前几天一直忙着期(lu)中(shi)没怎么搞acm,正好考完了试把这个小练习给刷一刷(顺便混一篇博客),这个题说实话还可以,但作为入门题的确难度有点高,我认为很多内容完全不是0基础的人可以做出来的,鉴于这次acm教学的报名人数之多以及大部分没什么基础的事实,可能最终结果会非常不容乐观。所以,我们无需为这次大部分教学题的一脸懵逼而有什么恐惧,因为从循序渐进的角度来讲掌握这些题只是时间问题,关键在于有没有花这个时间,我认为对于大多数人只要把第一题搞定就已经很不错了,因为这是基本功题,完全考察的是读题审题以及代码实现的功力,和思想没什么关系,所以我建议一定要做好这道题,另外虽然题目很难,我还是觉得有必要体验一下,现在有四百多个报名,可是参赛的感觉连六分之一都不到,说实话我觉得这是个态度问题,把一件事做好首先要端正态度。对于那种跟风而来,看着别人搞什么就去人云亦云,却不思进取的人,我认为他们是什么也搞不好的。如果没有拿出付出的觉悟,我倒建议不要参加了,免得浪费时间,当然所谓的付出不尽然是要参加什么什么大赛之类的,最重要的是学习知识和思想,我觉得这半年多看挑战程序设计让我见到了很多,也学到了很多,这些思想很多会终身受用。
好了,说了这么多,还是切入正题吧
这些题大部分要么是xdoj的原题,要么就是其他oj上的题,所以还是很熟悉的。
A
校赛的第二题,之前ac的代码用不了了,在好好读了一下题之后做出了一些更新。先说说题意,第一行给了一些字母,之后给出若干单词,要求把能用上述字母拼出的单词的字母数的最大值求出来,重点在于字母是不能重复使用的,并且给出字母是没有顺序的。
贴代码
# include
# include
inline int max(int a , int b)
{
return (a > b) ? a : b;
}
int main()
{
char s[30], c[30];
int n[26];
int m[26];
while(~scanf("%s", s))
{
memset(n , 0 , sizeof(n));
int i, j;
for(i = 0 ; s[i] ; i++)
n[s[i] - 'a']++;
int N;
scanf("%d", &N);
int ans = 0;
for(i = 0 ; i < N ; i++)
{
memset(m , 0 , sizeof(m));
scanf("%s", c);
for(j = 0 ; c[j] ; j++)
{
m[c[j] - 'a']++;
if(m[c[j] - 'a'] > n[c[j] - 'a'])
break;
}
if(!c[j])
ans = max(ans , j);
}
printf("%d\n", ans);
}
return 0;
}
B
还是原题,之前在现场赛已经说明过,可以发现第i小的会出现i-1次,直接一个一个推即可
贴代码
# include
# include
using namespace std;
const int MAX_N = 2500;
int A[MAX_N];
int main()
{
int T;
int N, K;
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &N, &K);
int i;
for(i = 0 ; i < N ; i++)
scanf("%d", &A[i]);
sort(A , A + N);
for(i = 1 ; i < N ; i++)
{
if(K <= i)
{
printf("%d\n", A[N - 1 - i]);
break;
}
K -= i;
}
}
return 0;
}
C
xdoj原题,大部分人都想复杂了,虽然是逆序对但是是简单逆序对,所以那些高级的方法(比如BIT)大可不用,只要用一个数组作桶记录之前的各个数字的出现的个数并且把逆序对一个一个求出来即可,复杂度也只有10n。
贴代码
# include
const int Mod = 1000000007;
int N, T;
int a[10];
void solve()
{
long sum = 0;
int i;
for(i = 0 ; i < 10 ; i++)
a[i] = 0;
for(i = 0 ; i < N ; i++)
{
int t;
scanf("%d", &t);
int j;
for(j = t + 1 ; j <= 9 ; j++)
sum += a[j];
a[t]++;
}
sum %= Mod;
printf("%ld\n", sum);
}
int main()
{
scanf("%d", &T);
int i;
for(i = 0 ; i < T ; i++)
{
scanf("%d", &N);
solve();
}
return 0;
}
D
其实这道是所有里面最简单的一道,好像也是以前校赛的签到题,直接按照题意求即可
贴代码
# include
int T, N;
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
int i = 1, sum = 1;
int ma = 0, mai;
while(N)
{
if(sum > N)
sum = N;
if(sum > ma)
{
mai = i;
ma = sum;
}
N -= sum;
i++;
sum *= 2;
}
printf("%d %d\n", mai, ma);
}
return 0;
}
F
xdoj原题,看着好像是背包其实根本不是,因为金粉是可以拿任意重量的,所以直接从单价最高的贪心即可,另外不建议在常规运算中使用double,因为会有精度问题,建议写成分数形式。
贴代码
# include
# include
using namespace std;
typedef pair<int , int> P;
const int MAX_N = 1000;
P box[MAX_N];
int N, V, T;
bool com(P a , P b)
{
return a.first * b.second > a.second * b.first;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
int i;
for(i = 0 ; i < N ; i++)
scanf("%d %d", &box[i].second, &box[i].first);
scanf("%d", &V);
sort(box , box + N , com);
double ans = 0;
for(i = 0 ; i < N ; i++)
{
if(V > box[i].second)
{
ans += box[i].first;
V -= box[i].second;
}
else
{
ans += (double)V * box[i].first / box[i].second;
break;
}
}
printf("%.4lf\n", ans);
}
return 0;
}
这些是xdoj里的原题,之后有一些题是差不多的,只是范围有变化,我就当一道题写了,所有代码保证两道题都可ac。
F&G
求最大的给定规则式,算是xdoj1010最优规则式的弱化版,思路是一样的,就是用一个dp来把a[i] - a[j]和a[i]的最大值保存起来,然后每次遇到新的数就更新他们。
贴代码
# include
const int MAX_N = 1e6;
int A[MAX_N];
int T, N;
inline int max(int a , int b)
{
return (a > b) ? a : b;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
int i;
for(i = 0 ; i < N ; i++)
scanf("%d", &A[i]);
int ans = -2e9, h = -2e9;
for(i = 0 ; i < N ; i++)
{
if(i)
ans = max(ans , h - A[i]);
h = max(h , A[i]);
}
printf("%d\n", ans);
}
return 0;
}
H&I
0和1的数字就是二进制,也就是说只要把比给定的这个数字小的最大二进制的值找出来即可,从开头开始看,如果是0或1那么这一位就只能是0或1,如果出现比1大的数字,那么之后的就可以全部是1,也就不用再看了,所以把这个数字求出来即可。
贴代码
# include
# include
typedef long long ll;
const int MAX_N = 1e6 + 7;
const int Mod = 1e9 + 7;
char s[MAX_N + 1];
ll po(ll a , ll b , ll mo)
{
ll ans = 1;
while(b)
{
if(b & 1)
ans = ans * a % mo;
b >>= 1;
a = a * a % mo;
}
return ans;
}
int main()
{
while(~scanf("%s", s))
{
int i, j;
int l = strlen(s);
for(i = 0 ; s[i] ; i++)
{
if(s[i] > 49)
break;
}
ll ans = po(2 , l - i , Mod) - 1;
ll n = ans + 1;
if(n >= Mod)
n -= Mod;
for(i-- ; i >= 0 ; i--)
{
if(s[i] == 49)
{
ans += n;
if(ans >= Mod)
ans -= Mod;
}
n <<= 1;
if(n >= Mod)
n -= Mod;
}
printf("%lld\n", ans);
}
return 0;
}
J
一道二分题,随便给定一个时间n,我可以很快判定这个n能不能成立,并且很明显比这个n大的都能成立,并且如果n不成立,比n小的也不成立,所以直接在所有范围内二分查找最优值即可。
贴代码
# include
typedef long long ll;
const int MAX_N = 1e5;
ll A[MAX_N], K;
int N, T;
bool ok(ll n)
{
int i;
ll ans = 0;
for(i = 0 ; i < N ; i++)
{
if(A[i] > n)
ans += (A[i] - n + K - 1) / K;
}
return ans <= n;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
int i;
for(i = 0 ; i < N ; i++)
scanf("%lld", &A[i]);
scanf("%lld", &K);
ll lb = 0, ub = 1e9;
while(ub - lb > 1)
{
ll mid = (lb + ub) / 2;
if(ok(mid))
ub = mid;
else
lb = mid;
}
printf("%lld\n", ub);
}
return 0;
}
K&L
这道题很迷,不知道是不是数据比较水,反正我感觉是有一些问题的,可以看出第一题的y是没有办法影响x的价格排名的,所以只要按照先x再y的顺序就是价值顺序。因为需要价格尽量大所以把商人的序列排序后从大开始考虑,这时候只要有一个y尽量小且x比他大的车跟他匹配即可(不需要管x,因为后面的x肯定不会更大),因为y比较小所以可以用桶排序,每次新出现一个更小的x值时就把那些新的x比他大的车投入桶中。不过这个算法如果碰到y的范围在1000的时候可能会有问题,因为按先x再y的顺序排序之后不一定是按照价格排序了,不过最后发现提交也没错,而且经过我的调查,这道题好像没有范围是1000的在各大oj里出现过,都是100的,所以可能是出题人的锅也说不定(滑稽)。
贴代码
# include
# include
using namespace std;
typedef pair<int , int> P;
typedef long long ll;
const int MAX_N = 1e5;
const int MAX_Y = 1000;
P A[MAX_N], B[MAX_N];
int box[MAX_Y + 1];
int N, M, T;
bool com(P a , P b)
{
return a > b;
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &N, &M);
fill(box , box + MAX_Y + 1 , 0);
int i, j, k;
for(i = 0 ; i < N ; i++)
scanf("%d %d", &A[i].first, &A[i].second);
for(i = 0 ; i < M ; i++)
scanf("%d %d", &B[i].first, &B[i].second);
sort(A , A + N , com);
sort(B , B + M , com);
j = 0;
ll m = 0, sum = 0;
for(i = 0 ; i < M ; i++)
{
while(j < N && A[j].first >= B[i].first)
{
box[A[j].second]++;
j++;
}
for(k = B[i].second ; k <= MAX_Y ; k++)
{
if(box[k])
{
box[k]--;
sum++;
m += 500 * B[i].first + 2 * B[i].second;
break;
}
}
}
printf("%lld %lld\n", sum , m);
}
return 0;
}
之后两道题在挑战程序设计中级篇3.2常用技巧中有一模一样的解答,有兴趣的可以看看书里怎么写的,非常详细,我就简单说一下好了。
M
尺取法样题
贴代码
# include
# define min(A , B) (A < B) ? (A) : (B);
const int INF = 100000000;
const int MAX_N = 100000;
int A[MAX_N + 1];
int N, S, T;
void solve()
{
int z = 0, y = 0, ans = INF;
while(y <= N)
{
while(A[y] - A[z] < S && y <= N)
y++;
if(y == N + 1)
break;
ans = min(y - z , ans);
z++;
}
if(ans == INF)
puts("0");
else
printf("%d\n", ans);
}
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d %d", &N, &S);
A[0] = 0;
int i;
for(i = 1 ; i <= N ; i++)
{
int tp;
scanf("%d", &tp);
A[i] = tp + A[i - 1];
}
solve();
}
return 0;
}
N
折半枚举样题,复杂度n方logn
# include
# include
using namespace std;
const int MAX_N = 4000;
int A[MAX_N], B[MAX_N], C[MAX_N], D[MAX_N], cas[MAX_N * MAX_N];
int N;
void solve()
{
int i, j;
for(i = 0 ; i < N ; i++)
for(j = 0 ; j < N ; j++)
cas[i * N + j] = C[i] + D[j];
sort(cas , cas + N * N);
long long ans = 0;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j < N ; j++)
{
int ts = -A[i] - B[j];
ans += upper_bound(cas , cas + N * N , ts) - lower_bound(cas , cas + N * N , ts);
}
}
printf("%lld\n", ans);
}
int main()
{
scanf("%d", &N);
int i;
for(i = 0 ; i < N ; i++)
{
scanf("%d", &A[i]);
scanf("%d", &B[i]);
scanf("%d", &C[i]);
scanf("%d", &D[i]);
}
solve();
return 0;
}
O
其实挺水的,但是藏的很深,仔细一看发现其实这题和宝石种类没有任何关系,只要把1到n分成两份和相等即可,所以对于和是奇数的直接pass,和是偶数的可以证明必存在方法,只要稍稍构造一下即可。
贴代码
# include
const int MAX_N = 1e5 + 7;
char A[MAX_N];
int N, T;
int main()
{
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
scanf("%s", A);
int c = N % 4;
if(c == 1 || c == 2)
{
puts("-1");
continue;
}
int i;
if(c == 0)
{
for(i = 0 ; i < N ; i++)
{
int k = 1;
if(i < (N / 4) * 3 && N / 4 <= i)
k++;
printf("%d", k + 2 * A[i] - 96);
}
}
else
{
for(i = 0 ; i < N - 1 ; i++)
{
int k = 1;
if(i < (N - 3) / 4 * 3 + 1 && (N - 3) / 4 + 1 <= i)
k++;
printf("%d", k + 2 * A[i] - 96);
}
printf("%d", 2 + 2 * A[i] - 96);
}
puts("");
}
return 0;
}