今年的题目出的有点诡异,难度跨越有点大
入门 to 普及- to(注意:前方东非大裂谷,请小心慢行) 提高+/省选- to 提高+/省选-
不过实际上没有这么难
T3、T4 一个DP 一个暴力(虽然不是正解) 也就可以过了
扯入正题
T1 标题统计
这道题十分的水,没有什么技术含量,随便怎么搞都可以过。
下面是我直接放代码了。。。
#include<bits/stdc++.h> using namespace std; char t; int ans(0); int main(){ freopen( "title.in", "r", stdin ); freopen( "title.out", "w", stdout ); while( ( t = getchar() ) != EOF ) if ( t != ' ' && t != '\n' && t != '\r' ) ans++; printf( "%d", ans ); return 0; }
T2 龙虎斗
这道题没话说,只是题目长了点,好好理解一下也是不难的。
我们可以预处理出两边阵营的气势和(别忘了加上“某一刻天降神兵”)然后枚举每个兵营,把你的兵加进去,算出之后两个阵营最终的气势,然后选出气势之差绝对值最小的哪个阵营就可以了。
话不多说,直接上代码(普及- 及以下难度的不用具体讲吧?)。还有注意要开 long long。(死了也别忘记)据说没开long long只能得70左右。
#include<bits/stdc++.h> using namespace std; typedef long long LL; LL n, m, p1, s1, s2; LL s[166666]; LL L, R; LL ans(-1), q(0x7f7f7f7f7f7f7f7f); LL Abs( LL x ){ return x >= 0 ? x : -x; } int main(){ freopen( "fight.in", "r", stdin ); freopen( "fight.out", "w", stdout ); scanf( "%lld", &n ); for ( int i = 1; i <= n; ++i ) scanf( "%lld", &s[i] ); scanf( "%lld%lld%lld%lld", &m, &p1, &s1, &s2 ); s[p1] += s1; for ( int i = 1; i <= n; ++i ){ if ( i < m ) L += ( m - i ) * s[i]; if ( i > m ) R += ( i - m ) * s[i]; } for ( int i = 1; i <= n; ++i ){ LL tL(L), tR(R), c; if ( i < m ) tL += ( m - i ) * s2; if ( i > m ) tR += ( i - m ) * s2; c = Abs( tL - tR ); if ( c < q ) ans = i, q = c; } printf( "%lld\n", ans ); return 0; }
T3 摆渡车
看这道题的时候,我(相信大家也是这样)最先想到的是贪心,但是从数据范围可以看出,如果是贪心题,数据范围不会那么小(相信NOIP不会和Luogu月赛一样,2018 11月月赛 搞个几百大小数据骗我们用DP,结果是贪心)。有些人会想(including me),是不是在有人到达时才能发车呢???没想清楚就下手的话,就会浪费好多时间。仔细想想,很容易发现不一定要有人到达时发车,比如有时候,bus一回来,有个人等了2分钟,后面那个人还有INF(hh) min 才会来,如果有人到达时才能发车,那么bus将在INF min后才等到一个人,原来等了一分钟的那个人与司机等得花都谢了,所以这时候肯定是一回来就发车,虽然没有人刚好到达。
我们可以考虑,如果两个人之间的时间差距 >= m 时 可以把差距直接变成 2m,因为bus最多等 m min(因为如果等的时间大于m,足够让bus往返一次,要更优),若前一个人的前1 min发车,那么bus回来开始等是(m - 1) min之后(以前一个人为基准),再等m min,也就是说最优方案肯定是在每个人来到时间的2m-1 min之内发过车,方便一点直接看成2m。
然后,DP。f[i]代表i时发过车的最优方案每个人等的时间和。
f[i] = min{ f[j] + (j + 1 到 i 之间到达的所有人等的时间之和) }(j <= i - m)
对于(j + 1 到 i 之间到达的所有人等的时间之和)的值,我们可以预处理出来,即将到达时间求前缀和,人数求前缀和,可以化为 (j + 1 到 i 之间到达的所有人人数和 * i) - (j + 1 到 i 之间到达的所有人来到的时间之和) 。
根据以上结论(bus最多等m min),我们可以进一步缩小j的范围,上次发车肯定在i - m之后。
这样的复杂度已经降到了O(m^2 *n)本来这样已经可以了,但是还有更优的做法,可以把复杂度进一步降到O(mn)级别。
比赛时我每想到缩小j的范围,我顺着贪心的思路:能不能用贪心来优化DP呢???于是就乱搞了一个"GDP"(greedy DP)(国民生产总值?hh)然后侥幸过了所有测试点。
我们可以考虑,当bus发车时(设为i)没有人到达,bus肯定刚刚回来,i - m肯定发过车。因为如果i之前就回来了,为什么不早点发车呢?如果早1 min,不但会影响上车人数,而且每人少等1 min何乐而不为?
所以i没有人刚到,i 发过车,但i - m每发过车绝对不是最优的,不管怎样,直接当做j只有一个取值——i - m。
这样做,虽然不能保证中间过程达到最优,但因为遵循最优的方案,结果不会出现错误。
然后上代码!(虽然不是最好的解,但89ms也凑合吧。 至少比原来几百ms好多了)
#include<bits/stdc++.h> using namespace std; const int MAXN = 500005; int n, m, tot; int a[666], c[666], f[MAXN], p[MAXN]; int dp[MAXN], ans; bool v[MAXN]; int M; int main(){ freopen( "bus.in", "r", stdin ); freopen( "bus.out", "w", stdout ); scanf( "%d%d", &n, &m ); for ( int i = 1; i <= n; ++i ) scanf( "%d", &a[i] ); sort( a + 1, a + n + 1 ); for ( int i = 1; i <= n; ++i ) c[i] = a[i] - a[i - 1]; for ( int i = 1; i <= n; ++i ){ if ( c[i] >= 2 * m ) c[i] = 2 * m; a[i] = a[i - 1] + c[i]; } for ( int i = 1; i <= n; ++i ){ M = max( M, a[i] ); v[a[i]] = 1, f[a[i]] += a[i], p[a[i]]++; } for ( int i = 1; i <= M + m; ++i ) f[i] += f[i - 1], p[i] += p[i - 1]; memset( dp, 0x7f, sizeof dp ); dp[0] = 0; for ( int i = 1; i <= M + m; ++i ){ if ( i <= m ){ dp[i] = p[i] * i - f[i]; continue; } if ( !v[i] ){ dp[i] = dp[i - m] + ( p[i] - p[i - m] ) * i - ( f[i] - f[i - m] ); continue; } for ( int j = max( 0, i - 2 * m ); j <= i - m; ++j ){ dp[i] = min( dp[i], dp[j] + i * ( p[i] - p[j] ) - ( f[i] - f[j] ) ); } } int ans(0x7f7f7f7f); for ( int i = M; i <= M + m; ++i ) ans = min( ans, dp[i] ); printf( "%d\n", ans ); return 0; }
T4 对称二叉树
这道题我也不知道正解是什么。我直接暴力+剪枝也跑过了所有测试点(数据太水?)
我们可以考虑当将一棵树所有节点的左右子树交换,那么搜索的顺序左变右,右变左。
即原来对于节点P,从根节点到P的路径是左右左左右,那么反转后根节点到P'的位置的路径就是右左右右左。
我们直接枚举每个子树的根节点,把原来的点、翻转后的点一一对应就可以了。
实现起来也不难。注意加点剪枝(不解释剪枝原理了)。
#include<bits/stdc++.h> using namespace std; const int MAXN = 1000000 + 0xac;//AC万岁!!! int n, v[MAXN], d[MAXN], s[MAXN]; unsigned long long M1[MAXN], M2[MAXN], M3[MAXN]; int L[MAXN], R[MAXN]; void DFS( int x, int dep ){ s[x] = 1; M1[x] = v[x]; M2[x] = v[x]; M3[x] = dep * v[x]; if ( L[x] != -1 ) DFS( L[x], dep + 1 ), s[x] += s[L[x]], M1[x] *= M1[L[x]], M2[x] += M2[L[x]], M3[x] += M3[L[x]]; d[x] = dep; if ( R[x] != -1 ) DFS( R[x], dep + 1 ), s[x] += s[R[x]], M1[x] *= M1[R[x]], M2[x] += M2[R[x]], M3[x] += M3[R[x]]; } bool check( int x, int y ){ if ( v[x] != v[y] || s[x] != s[y] || M1[x] != M1[y] || M2[x] != M2[y] || M3[x] != M3[y] ) return 0; if ( L[x] > 0 || R[y] > 0 ){ if ( L[x] < 0 || R[y] < 0 ) return 0; if ( !check( L[x], R[y] ) ) return 0; } if ( R[x] > 0 || L[y] > 0 ){ if ( R[x] < 0 || L[y] < 0 ) return 0; if ( !check( R[x], L[y] ) ) return 0; } return 1; } bool cmp( int x, int y ){ return s[x] > s[y]; } int main(){ freopen( "tree.in", "r", stdin ); freopen( "tree.out", "w", stdout ); scanf( "%d", &n ); for ( int i = 1; i <= n; ++i ) scanf( "%d", &v[i] ); for ( int i = 1; i <= n; ++i ) scanf( "%d%d", &L[i], &R[i] ); DFS( 1, 1 ); int ans(1); for ( int i = 1; i <= n; ++i ) if ( s[i] > ans && check( i, i ) ) ans = max( ans, s[i] ); printf( "%d\n", ans ); return 0; }
完结撒花!!!