The Qualification Round has finished, and tutorials for this round have been posted here.
Thanks a lot for all the task authors and all those who help contestants with problems.
The Online Round is on the way, and the tutorials will be posted here after contest as soon as possible.
I recommend a total of nine programming tasks for all of you.
They are not so easy to solve, but I think most will solve at least one.
Top contestants will advance to the Onsite Round.
As I am not stay in China (actually, United States), I will be absent during the contest.
I am so sorry for that I cannot provide any Question & Answer for you at that moment.
But, fortunately, you could ask those who are marked by "答疑" from the QQ group for necessary help.
If you want to communicate with me about anything, please contact me with QQ (906091877).
Best,
Hanzhou Wu
2014/11/8
===============================================================================
你们好,亲爱的朋友们:
网络赛进展如意吗?我想无论你解出了几道题,你都会觉得我们的比赛至少不是应试教育。
我们的比赛考察选手如何运用已有的知识解决问题,或者将理论知识转化为程序实践,当然,远远不止这些。
当你多做出了一道题,我想你会欣喜万分;当你发现本该简单的题,却迟迟得不到Yes,你很烦闷;当你发现有很多伙伴,跑在你的前面,你会发现自己或许还有很多欠缺;无论如何,你似乎通过一场比赛,收获了一些,懂得了一些。
我想你会发现,虽然你在高中数学经常拿高分,又或者在高等数学、线性代数方面信心满满,你仍会觉得你的数学水平还需要在某些方面得到提高。
你会发现,这次比赛对数学问题或思维的考察只是冰山一角,在接下来的日子里,如果你想在ACM比赛当中有所建树,你需要做好嫁给数学这位大哥的打算。
如果你愿意为成为一名出色的ACMer而努力,你要清楚这条道路上曲折万千、异常艰辛,随时有可能中途退缩,可一旦走出来,也将是万众瞩目。我想道理每位同学都懂,多说无益,反倒是真正用行动去做到持之以恒变得无比的难能可贵。
总之,非常欢迎你加入我们的大团队,并寄希望于你能为你自己、为我们、也为学校,在不久的将来争得荣誉。为愿意踏上ACM比赛这条贼船的朋友们践行:曾不知路之曲直兮,南指月与列星。
===============================================================================
网络预选赛参考题解:
【A题:世界是平的】
题意:判断这N个不同的点是否可能共面。(通过样例数据也可以推断出来)
1、如果点的个数不超过3,则存在共面情况。
2、如果所有的四元组(P[0],P[1],P[2],P[k]), (2 < k <N) 共面,则存在共面,需要特别处理四元组的共线情况。
【B题:爱恨就在一瞬间】
题意:计算最多的可共线点数。
1、二维版本:Lining Up
2、本题是三维版本,且数据量大,但解题思路不变。
3、解题思路:枚举每一个点P[k],计算所有的其他的点与该点的斜率,按斜率统计,并更新最优值。
const int maxN = 1000 + 10; int gcd(int a, int b) { if( b == 0 ) return a; return gcd(b, a % b); } struct point3 { int x, y, z; point3() {} point3(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {} inline point3 operator-(const point3 &s) const { return point3(x - s.x, y - s.y, z - s.z); } inline void unit() { int d = gcd(x, gcd(y, z)); if( d ) x /= d, y /= d, z /= d; if( x < 0 ) x = -x, y = -y, z = -z; if( x == 0 && y < 0 ) x = -x, y = -y, z = -z; if( x == 0 && y == 0 && z < 0 ) x = -x, y = -y, z = -z; } inline bool operator<(const point3 &s) const { if( x == s.x && y == s.y ) return z < s.z; if( x == s.x ) return y < s.y; return x < s.x; } inline bool operator==(const point3 &s) const { return x == s.x && y == s.y && z == s.z; } inline void in() { scanf("%d %d %d", &x, &y, &z); } }; point3 p[maxN]; int n; point3 q[maxN]; int nq; int gao(int idx) { nq = 0; for(int i = idx + 1; i < n; i ++) q[nq ++] = p[i] - p[idx]; for(int i = 0; i < nq; i ++) q[i].unit(); sort(q, q + nq); int res = 0; for(int i = 0; i < nq; ) { int j = i; while( j < nq && q[i] == q[j] ) j ++; res = max(res, j - i); i = j; } return 1 + res; } int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); while( scanf("%d", &n) == 1 ) { for(int i = 0; i < n; i ++) p[i].in(); int res = 0; for(int i = 0; i < n; i ++) if( n - i < res ) break; else res = max(res, gao(i)); printf("%d\n", res); } return 0; }
题意:判断是否存在可划分的三维平面。
1、这是 PLA (Perceptron Learning Algorithm) 算法落在三维下的具体实例。
2、这题测试数据经过了人工推演,赛前没有卡朴素的PLA实现,比赛过程中AC的程序是朴素实现。
【D题:反垃圾邮件】
类型:排序+模拟
const int maxN = 100000 + 10; struct node { int p, q; node() {} node(int _p, int _q) : p(_p), q(_q) {} inline void in() { scanf("%d %d", &p, &q); } inline bool operator<(const node &s) const { return p < s.p; } }; node v[maxN]; int N, T, x, y; bool sgn[maxN]; int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); while( scanf("%d %d %d %d", &N, &T, &x, &y) == 4 ) { if( N == 0 ) { printf("0\n"); continue; } for(int i = 0; i < N; i ++) v[i].in(); sort(v, v + N); memset(sgn, 0, sizeof(bool) * N); sgn[0] = 1; int res = 1, now = 0, self = (v[0].q == 1), fw = (v[0].q == -1); for(int i = 1; i < N; ) { if( v[i].p - v[now].p <= T ) { while( now < i && !sgn[now] ) now ++; if( now == i ) { sgn[i] = 1; res ++; self += (v[i].q == 1), fw += (v[i].q == -1); i ++; continue; } if( self + (v[i].q == 1) > x ) { i ++; continue; } if( fw + (v[i].q == -1) > y ) { i ++; continue; } sgn[i] = 1; res ++; self += (v[i].q == 1), fw += (v[i].q == -1); i ++; } else { while( now < i && (v[i].p - v[now].p > T) ) { if( sgn[now] && v[now].q == 1 ) self --; if( sgn[now] && v[now].q == -1 ) fw --; now ++; } while( now < i && !sgn[now] ) now ++; } } printf("%d\n", res); } return 0; }
说明:关键在于运行时间慢,因为N的范围达到10^9,且所有数据类型都是int型,所以第2个for循环的k*k会出现数据溢出,采用long long 存储,并优化代码即可。
long long sum_k(long long lft, long long rht, long long Mod) { if( lft > rht ) return 0LL; long long sum = (rht - lft + 1LL) * (lft + rht) / 2LL; return sum % Mod; } long long gao(long long n, long long Mod) { long long p = n * (n + 1LL) / 2LL; long long q = 2LL * n + 1LL; if( p % 3LL == 0 ) p /= 3LL; else if( q % 3LL == 0 ) q /= 3LL; else dbg("error..."); p %= Mod, q %= Mod; return (p * q) % Mod; } long long sum_k2(long long lft, long long rht, long long Mod) { if( lft > rht ) return 0LL; long long ans1 = gao(lft - 1LL, Mod); long long ans2 = gao(rht, Mod); long long ans = (ans2 - ans1) % Mod; if( ans < 0LL ) ans += Mod; return ans; } int gao(int _N, int _M) { long long N = (long long)_N; long long M = (long long)_M; long long res_1 = sum_k(1LL, N / 2LL - 1LL, M + 1LL); long long res_2 = sum_k2(N / 2LL, N - 1LL, M + 1LL); return (res_1 + res_2) % (M + 1LL); } int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); int N, M; while( scanf("%d %d", &N, &M) == 2 ) { int res = gao(N, M); printf("%d\n", res); } return 0; }
说明:这是很简单的题,推算一遍,可以发现所查询的球的位置是可以模拟推算出来的。
int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); int nt; scanf("%d", &nt); int n, k; for(; nt > 0; nt --) { scanf("%d %d", &n, &k); int pos = k; for(int i = 0; i < n; i ++) { if( pos >= i ) pos = (n - 1) - (pos - i); else break; } printf("%d\n", pos); } return 0; }
说明:只需要去计算每一个元素在最终的表达式中出现的次数即可,如果这个元素出现偶数次,表明这个元素对最终的表达式没有贡献(也即可以不用出现在最终的表达式中),如果是奇数次,则需要这个元素。判断某个元素出现多少次,只需要统计这个元素左边有多少个元素(假设X个),右边有多少个元素(假设Y个),则在最终的表达式中该元素会出现(X+1)*(Y+1)次。
int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); int nt; scanf("%d", &nt); for(; nt > 0; nt --) { int n; scanf("%d", &n); int res = 0, x; for(int i = 1; i <= n; i ++) { scanf("%d", &x); if( (i & 1) & (n - i + 1) ) res ^= x; } printf("%d\n", res); } return 0; }
类型:动态规划(Dynamic Programming,DP)
1、去重预处理。
2、去重后,只剩1个元素、或者存在2个元素的GCD为1,结果特殊处理即可(并对含有元素1的序列特殊处理)。
3、定义 dp[i][j] 表示由前 i 个元素构造出最大公约数为 j 时需要的最少个数(对应于删除最多的个数)。
计算 d = GCD(A[i],j),如果:
a) d = j: 表示添加A[i]后,GCD不改变,那么A[i]相对于j来说可以不需要, 即:dp[i][j] =dp[i-1][j]。
b) d ≠ j: 表示添加A[i]后,GCD改变了,那么A[i]相对于d 来说是需要的,则:dp[i][d] = dp[i-1][j] + 1。
最后的结果是:n -dp[n][X]
4、可以将二维DP转化到一维DP。
const int maxN = 500 + 2; const int maxV = 10000 + 2; int n, res; int v[maxN], dp[maxV]; bool vst[maxV]; inline int gcd(int a, int b); int gao() { int i, j; for(i = 0; i < n; i ++) for(j = i + 1; j < n; j ++) if( gcd(v[i], v[j]) == 1 ) return 1; return 0; } int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); int i, j, k; memset(vst, false, sizeof(bool) * maxV); while( scanf("%d", &n) == 1 ) { for(i = 0, j = 0, k = maxV; i < n; i ++) { scanf("%d", &res); if( !vst[res] ) vst[res] = true; if( res > j ) j = res; if( res < k ) k = res; } res = n, n = 0; for(i = k; i <= j; i ++) if( vst[i] ) { v[n ++] = i; vst[i] = false; } if( n == 1 || v[0] == 1 ) { printf("%d\n", res - 1); continue; } if( gao() ) { printf("%d\n", res - 2); continue; } memset(dp, 0x3f, sizeof(int) * (v[n - 1] + 1)); for(i = 0; i < n; i ++) { for(j = 1; j < v[i]; j ++) { if( v[i] % j ) { k = gcd(v[i], j); if( dp[k] > dp[j] + 1 ) dp[k] = dp[j] + 1; } } dp[v[i]] = 1; } k = v[0]; for(i = 1; i < n; i ++) k = gcd(v[i], k); printf("%d\n", res - dp[k]); } return 0; } inline int gcd(int a, int b) { if( b == 0 ) return a; return gcd(b, a % b); }
说明:送分题,最坏情况下,先冒泡排序,然后计数即可。
const int maxN = 100 + 2; int n; int v[maxN]; int main() { //freopen("data.in", "r", stdin); //freopen("data.out", "w", stdout); while( scanf("%d", &n) == 1 ) { for(int i = 0; i < n; i ++) scanf("%d", &v[i]); sort(v, v + n); n = unique(v, v + n) - v; printf("%d\n", n); } return 0; }