您的位置:首页 > 理论基础 > 数据结构算法

2011年"新秀杯"程序设计比赛——现场决赛参考解题报告和个人总结

2011-11-13 20:25 585 查看
本来没打算写解题思路或提供参考程序的,因为什么都给了,你们就没时间去思考什么了,不过从去年开始,基本上每次都提供的参考程序和解题思路,突然中断,毕竟不好!于是,又打广告了...

大赛总结

这次比赛总体上来说,办的还是成功的!

和今年4月份的“编程之美”ACM校内程序设计比赛相比较,算得上分庭抗礼(如果没有记错,“编程之美”校赛进入了教务处的学科竞赛日志,说明是成功的);校赛时,只有22支队伍参加决赛,这次“新秀杯”比赛有69人参加,其中成都校区60人,峨眉校区9人。

不过,这次“新秀杯”比赛的不足是高估了区分度,“你做或者不做,就那9道题,你A或者不A,最多过3道,不增只减”,所以赛后给奖也很为难的,即便我想给大家奖,可是李老师的50%上限摆在那,我一开始打算成都校区和峨眉校区分开评奖,但是看到现场比赛的情况,我觉得这么做是不对的,分开评奖太见外了。所以,临时决定两校区同时参与评奖,同等对待。这也就是为什么最后只有34个人获奖,并没有40人。按照现场统计的结果,获奖情况(暂定)如下:

一等奖:峨眉校区1名(排名第1),成都校区3名(其中有2名是女生)

二等奖:峨眉校区3名,成都校区11名

三等奖:峨眉校区4名,成都校区12名

累计:峨眉校区8名(共9名),成都校区26名(共60名),共34名(34 = 69 / 2)

本来二等奖不会有这么多的,不然二、三等奖没有区分度,也许是大家都做出了3道题,所以才多评二等奖,O(∩_∩)O~

但是,一等奖我是给的很有原则的,除了1个峨眉校区同学完成4道题,其余都是不超过3道题的,获得一等奖的两名女生都做出了3道题,虽然排名不在前列,但是给她们一等奖是实至名归。我想,你们也没有什么意见,也不应该有意见。

这次比赛,即便你认为是数学专场,你也不应该有什么怨言。ACM不是考试,它没有考试范围,如果有范围,地区赛中那么多的DP专场,怎么说?这是没道理的。何况,除了图论没怎么细致性的涉及,贪心,动归,数据结构,模拟,数论,组合,博弈,二分,矩阵,排序都考虑到了,只是这些东西都嵌入到的题目当中去了,但确实又是考到了。

说到这里,我对一年级同学的表现称赞,看来“新秀杯”这个名字取的有道理,对峨眉校区同学完成4题,也称赞,他是全场唯一(除去友情参赛同学)解出4道题的选手,当我看到名字后,我说这人名我怎么不熟悉呢?果不其然!

但是,参加校队(含友情参赛)却不怎么近人意,不用摆那么多理由,我已经不是正规的参赛队员了,不用和我说什么偏题不偏题,你能说我的题目没有价值吗?要做好ACM,就得适应各类场景,对比赛抱怨太多,说明你还是把比赛看成考试了!建议大家向全能选手发展!

我想,你们经历过这么多风雨,除了A题没怎么触及到,其他题目都是常规的,有机会过掉的,即便某几道有困难,但不至于除了最简单的3道,其他的都A不出来,着实差强人意。

总之,各位加油,前面的路还很坎坷,要用心去学习,切忌急功近利~

欢迎一年级的同学加入到ACM团队中,给你们的建议就是,现在你们正处在接触和学习ACM的好时期,切不可错过,而且希望你们一定要持之以恒,用心专一,不浮躁。

祝峨眉校区ACM事业越来越好!

欢迎广大同学参加明年的校赛,明年校赛不见不散~

解题报告

A题 玩石子游戏

N个石子的环,Alice无论怎么移除,剩下的N-M个都是连续的;不妨认为是一条直线,对于一条直线,每一次移除,都会剩下2个部分,求出这两部分的SG值(Sprague-Grundy),就可以得出当前状态的一个后继。求出所有的后继SG值,就可以得出当前状态的SG值。最后,如果N-M的SG值为0,Alice羸,否则Bob羸。

#include<cstdio>
#include<cstring>

int SG[1024];

int calc(int N, int M)
{
if( SG
!= -1 ) return SG
;
if( N < M ) return (SG
= 0);
bool vst[1024]; memset(vst, 0, sizeof(vst));
for(int i = 0; i <= N - M - i; ++i)
vst[calc(i, M) ^ calc(N - M - i, M)] = 1;
int gx = 0; while( vst[gx] ) ++gx;
return (SG
= gx);
}

int main()
{
int nT, idx = 0, N, M;
scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%d %d", &N, &M);
if( N < M ) printf("Case #%d: Bob\n", ++idx);
else {
memset(SG, -1, sizeof(SG));
if( calc(N - M, M) ) printf("Case #%d: Bob\n", ++idx);
else printf("Case #%d: Alice\n", ++idx);
}
}
return 0;
}


B题 安全信道

容易得到递推方程如下

F(n)=2*F(n-1)+2*F(n-2)

F(1)=3,F(2)=8

接下来,你可以用矩阵+二分,或者计算循环节打表计算,两个方法都是可以的,这里仅提供矩阵+二分通过的参考程序

#include<cstdio>
#include<cstring>

const int Mod = 2011;

struct Mat
{
int M[2][2];

inline void setE() {
M[0][0] = 1; M[0][1] = 0;
M[1][0] = 0; M[1][1] = 1;
}

inline void setU() {
M[0][0] = 2; M[0][1] = 2;
M[1][0] = 1; M[1][1] = 0;
}

inline Mat operator*(const Mat & s) const {
Mat ret; memset(ret.M, 0, sizeof(ret.M));
for(int k = 0, i, j; k < 2; ++k)
for(i = 0 ; i < 2; ++i)
for(j = 0; j < 2; ++j)
ret.M[i][j] = (ret.M[i][j]
+ M[i][k] * s.M[k][j]) % Mod;
return ret;
}
};

Mat exp_mod(Mat a, int b)
{
if( b == 0 ) {
a.setE();
return a;
}

if( b == 1 ) return a;

Mat ret = exp_mod(a, b >> 0x1);
ret = ret * ret;
if( b & 0x1 ) ret = ret * a;
return ret;
}

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
int idx = 0, nT, N, ans; Mat ret;
scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%d", &N);

printf("Case #%d: ", ++idx);

if( N == 1 ) printf("%d\n", 3);
else if( N == 2 ) printf("%d\n", 8);
else {
ret.setU();
ret = exp_mod(ret, N - 2);
ans = (ret.M[0][0] * 8 + ret.M[0][1] * 3) % Mod;
printf("%d\n", ans);
}
}
return 0;
}


C题 美国大选

对于候选人,当获得26个州的支持,才可以当选,要获得一个州的支持,需要有超过一半以上人数支持。现在问题是求当选,需要的最少支持人数,只需对输入的50个数值从小到大排序,取前26个数,每个数值取一半再加1,再求和即可。对二年级,属送分题;对一年级,考察排序算法。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<algorithm>

using namespace std;

int A[50];

int main()
{
//freopen("data2.in", "r", stdin);
//freopen("data2.out", "w", stdout);
int idx = 0, nT; scanf("%d", &nT);
while( (nT --) > 0 ) {
for(int i = 0; i < 50; ++i) scanf("%d", A + i);
sort(A, A + 50);
int ans = 0;
for(int i = 0; i < 26; ++i) ans += (A[i] + 2) >> 1;
printf("Case #%d: %d\n", ++idx, ans);
}
return 0;
}

D题 交大四人行

方法1:树状数组,题目已经说明人的相对高度不等,对于H(A)<H(B)<H(C)<H(D),枚举H(B)和H(C),利用树状数组计算合法的<H(A),H(D)>组合即可;同一思想,计算H(A)>H(B)>H(C)>H(D),累加求和即可。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxN = 1024;

int lb[maxN];
int N; int A[maxN], B[maxN];

void init()
{
lb[0] = 0;
for(int i = 1; i < maxN; ++i)
lb[i] = i & (-i);
}

int C[maxN];
int D[maxN]; // 记录比A[i]小的数的个数

void update(int pos, int val)
{
while( pos <= N ) {
C[pos] += val;
pos += lb[pos];
}
}

int sum(int pos)
{
int ret = 0;
while( pos > 0 ) {
ret += C[pos];
pos -= lb[pos];
}
return ret;
}

int BS(int &key)
{
static int l, h, m;
l = 0, h = N - 1;
while( l <= h ) {
m = (l + h) >> 1;
if( B[m] == key ) return m + 1;
if( B[m] > key ) h = m - 1;
else l = m + 1;
}
return 0;
}

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
//freopen("test.out", "r", stdin);
int idx = 0, nT; scanf("%d", &nT);
init();
while( (nT --) > 0 ) {
scanf("%d", &N);
for(int i = 0; i < N; ++i) {
scanf("%d", A + i);
B[i] = A[i];
}

sort(B, B + N);
for(int i = 0; i < N; ++i)
A[i] = BS(A[i]);

//printf("%d\n", N);
//for(int i = 0; i < N; ++i) printf("%d ", A[i]);
//printf("\n");

memset(C, 0, sizeof(C));
for(int i = 0; i < N; ++i) {
D[i] = sum(A[i] - 1);
update(A[i], 1);
}

int ans = 0;

for(int i = 0, j; i < N; ++i)
for(j = i + 1; j < N; ++j)
if( A[j] > A[i] )
ans = (ans + D[i] * (N - A[j] - j + D[j]) ) % 2012;
else
ans = (ans + (i - D[i]) * (A[j] - 1 - D[j])) % 2012;

printf("Case #%d: %d\n", ++idx, ans);
}
return 0;
}


方法2:动态规划,DP[i][j]=sum{ DP[k][j-1] | 0 < k < i}

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxN = 1000 + 2;

int N;
int A[maxN];

int dp[maxN][5];

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
int idx = 0, nT; scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%d", &N);
for(int i = 1; i <= N; ++i) scanf("%d", A + i);

memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; ++i) dp[i][1] = 1;
for(int i = 2, j, k; i <= N; ++i)
for(j = 2; j <= 4; ++j)
for(k = 1; k < i; ++k) {
if( A[i] > A[k] ) {
dp[i][j] += dp[k][j - 1];
dp[i][j] %= 2012;
}
}

int ans = 0;
for(int i = 4; i <= N; ++i) { ans += dp[i][4]; ans %= 2012; }
for(int i = 1, j = N; i < j; ++i, --j) swap(A[i], A[j]);

memset(dp, 0, sizeof(dp));
for(int i = 1; i <= N; ++i) dp[i][1] = 1;
for(int i = 2, j, k; i <= N; ++i)
for(j = 2; j <= 4; ++j)
for(k = 1; k < i; ++k) {
if( A[i] > A[k] ) {
dp[i][j] += dp[k][j - 1];
dp[i][j] %= 2012;
}
}

for(int i = 4; i <= N; ++i) { ans += dp[i][4]; ans %= 2012; }

printf("Case #%d: %d\n", ++idx, ans);
}
return 0;
}


E题 课后作业

如图所示



参考程序(注意,这里是"%lld",OJ要求的是"%I64d")

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<iostream>
#include<algorithm>

using namespace std;

typedef long long i64d;

const int maxN = 40000;

int prime[maxN >> 1];
bool vst[maxN];
int tot_prime = 0;

// 筛选法获得素数表
void init()
{
memset(vst, 0, sizeof(vst));
prime[tot_prime++] = 2;
for(int i = 3, j; i < maxN; i += 2)
if( !vst[i] ) {
prime[tot_prime++] = i;
for(j = i + i; j < maxN; j += i)
vst[j] = 1;
}
//printf("tot_prime = %d\n", tot_prime);
}

i64d N, M, P;
int fact[30], times[30], nf;
int divs[3000], nd;
// note 2^3 * 3 * 5 * 7 * 11 * 13 * 17 * 19 * 23 = 892371480 have 4 * (2^8) = 2^10 = 1024 divs
// note 2^2 * 3^3 * 5^2 * 7 * 11 * 13 * 17 * 19 = 872972100 have 1152 divs

// 枚举约数
void dfs(i64d curVal, int curPos)
{
if( curPos == nf ) {
divs[nd++] = curVal;
return ;
}

i64d tmp;
for(int i = 0, j; i <= times[curPos]; ++i) {
tmp = curVal;
for(j = 0; j < i; ++j) tmp *= fact[curPos];
dfs(tmp, curPos + 1);
}
}

// 快速模指数算法
i64d exp_mod(i64d &A, int B, i64d &P)
{
if( A == 0LL ) return 0LL;
if( B == 0LL ) return 1LL % P;
if( B == 1LL ) return A % P;
i64d ret = exp_mod(A, B >> 0x1, P);
ret = (ret * ret) % P;
if( B & 0x1 ) ret = (ret * A) % P;
return ret;
}

// 得到欧拉函数
i64d phi(i64d n)
{
i64d ret = n;
for(int i = 0; i < nf; ++i)
if( n % fact[i] == 0 )
ret = (ret / fact[i]) * (fact[i] - 1);
return ret;
}

// 计算函数
i64d calc()
{
M %= P; if( M == 0LL ) return 0LL;

nf = 0; i64d _N = N; // 大数分解
for(int i = 0; i < tot_prime && prime[i] * prime[i] <= _N; ++i) {
if( N % prime[i] == 0LL ) {
fact[nf] = prime[i]; times[nf] = 0;
do {
++times[nf]; _N /= prime[i];
} while( _N % prime[i] == 0 );
++nf;
}
}
if( _N > 1LL ) { times[nf] = 1; fact[nf++] = _N; }

//for(int i = 0; i < nf; ++i)
//	printf("%d --> %d\n", fact[i], times[i]);

nd = 0; dfs(1LL, 0);

//for(int i = 0; i < nd; ++i)
//	printf("<----- %d\n", divs[i]);
//printf("nd = %d\n", nd);

i64d ans = 0LL;
for(int i = 0; i < nd; ++i) {
ans += (phi(N / divs[i]) * exp_mod(M, divs[i], P)) % P;
ans %= P;
}

return ans % P;
}

int main()
{
//time_t s, e; s = clock();
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
init(); int idx = 0, nT;
scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%lld %lld %lld", &N, &M, &P);

printf("Case #%d: %lld\n", ++idx, calc());
}
//e = clock();
//cout << (double)(e-s) << endl;
return 0;
}


F题 校验编码

这题没什么好说的了,秒杀题。

#include<cstdio>
#include<cstring>

const int maxN = 1024;

char str[maxN];

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
int idx = 0, nT; scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%s", str);

int totOne = 0;
for(int i = 0; str[i]; ++i)
if( str[i] == '1' )
++totOne;

printf("Case #%d: %s", ++idx, str);
if( totOne & 0x1 ) printf("1\n");
else printf("0\n");
}
return 0;
}


G题 无向图序列

Havel-Hakimi Theory

由非负整数组成的非递增序列 { S: d(1), d(2), ... , d(n) } 是可图的,当且仅当序列 { S: d(2)-1, d(3)-1, …, d(d(1)+1)-1, d(d(1)+2), …, d(n) }是可图序列。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxN = 20 + 2;

int N;
int Deg[maxN];

inline bool cmp(int x, int y) { return x > y; }

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
int idx = 0, nT; scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%d", &N);
for(int i = 0; i < N; ++i) scanf("%d", Deg + i);
sort(Deg, Deg + N, cmp);

bool ok = 1;
while( ok ) {
if( Deg[0] < 0 ) { ok = 0; break; }
if( Deg[0] == 0 ) break;
if( Deg[0] > N - 1 ) { ok = 0; break; }
if( Deg[ Deg[0] ] < 1 ) { ok = 0; break; }
for(int i = 1; i <= Deg[0]; ++i) Deg[i] -= 1;
Deg[0] = 0; sort(Deg, Deg + N, cmp);
--N;
}

if( ok ) printf("Case #%d: YES\n", ++idx);
else printf("Case #%d: NO\n", ++idx);
}
return 0;
}

H题 竞争情报

这题我不给程序了,就提供几组测试数据,你自己来检查一下。

输入

10
JLEwx;+?o9bH`"|6666r%BoXCQ\+e@+i^z-.gto&j69
B>5b"[($V'j)MN0ll\r;#<'Y';1-yy}8%X9=]rPg999
123n<5;S{oM4s9*}Lv-M/x|H"BQS]bSz/HlIuc"r`6_
1EL9nN^at5{76+WOx2VZJd8888L~j^m`g?E[[Z=WdYZ
5^sN7|V~?%N%e7uTyl7M*CV:%5686G=p|_J52Jh7Fb5
6#J^/pfcSD5+1111vLu~xvXdtq*7)1GyYIVF9|vk=5i
76QP_rlOkLC#5B934wK}lNh;_'jGmHZ\py7eL~{_cJp
"9V.<d5Z9H1?JfY'Bw\rQ<37x<S&00703b-%xb02UR<
tPAh&h(3980w@Q5PicN/vULt*h@c}Ont;<'Ua,)i-p~
00tUrA-Hc:T#7yN`;JY+'-rD%\SV`{)e'9;Lt[^$}~0
输出

Case #1: 9 6666 69
Case #2: 5 0 1 8 9 999
Case #3: 123 5 4 9 6
Case #4: 1 9 5 76 2 8888
Case #5: 5 7 7 7 5686 52 7 5
Case #6: 6 5 1111 7 1 9 5
Case #7: 76 5 934 7
Case #8: 9 5 9 1 37 703 2
Case #9: 3980 5
Case #10: 0 7 9 0


I题 信息楼

稍难的模拟题,考察选手利用给定信息,得出问题的计算解析式,考察了选手思考问题和解决问题的能力。

一个陷阱:当人的起始楼层和目标楼层相同的时候,其到达目标楼层的最近时刻和返回原始层的最近时刻应该相等。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>

using namespace std;

int N, S, F, T, C;

int calc(int s, int f, int t)
{
static int M = 18;
if( s == f ) return t;

s -= 1, f -= 1;
int dir = t / (M - 1), mod;

if( t % (M - 1) == 0 ) {
if( dir & 1 ) mod = M - 1; // calc the location of elevator
else mod = 0;
} else {
if( dir & 1 ) mod = M - 1 - t % (M - 1);
else mod = t % (M - 1);
}

if( s < f ) {
if( dir & 1 ) {
t += mod;
mod = 0;
} // if to Left

if( mod <= s ) return t - mod + f;
return t + (M - 1 - mod) + (M - 1) + f;
}

if( !(dir & 1) ) {
t += (M - 1 - mod);
mod = M - 1;
} // if to Right

if( mod >= s ) return t + mod - f;
return t + mod + (M - 1) + (M - 1 - f);
}

int main()
{
//freopen("data.in", "r", stdin);
//freopen("data.out", "w", stdout);
//freopen("test.out", "r", stdin);
int idx = 0, nT, ret; scanf("%d", &nT);
while( (nT --) > 0 ) {
scanf("%d", &N);
printf("Case #%d:\n", ++idx);
for(int i = 0; i < N; ++i) {
scanf("%d %d %d %d", &S, &F, &T, &C);

printf("%d ", ret = calc(S, F, T));
if( S == F ) printf("%d\n", ret);
else printf("%d\n", calc(F, S, ret + C));
}
}
return 0;
}


Email: wuhanzhou_2007@126.com

Hzwu@SWJTU@2011.11.13
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐