NOIP 2018 普及組 解題報告
今年的題目出的有點詭異,難度跨越有點大
入門 to 普及- to(注意:前方東非大裂谷,請小心慢行) 提高+/省選- to 提高+/省選-
不過實際上沒有這麼難
T3、T4 一個DP 一個暴力(雖然不是正解) 也就可以過了
扯入正題
T1 標題統計
這道題十分的水,沒有什麼技術含量,隨便怎麼搞都可以過。
下面是我直接放程式碼了。。。
#include<bits/stdc++.h> using namespace std; char t; int ans(0); int main(){ freopen(T1 標題統計"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; }T2 龍虎鬥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; }T3 擺渡車
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; }T4 對稱二叉樹
完結撒花!!!