NOIP 2018 普及組 解題報告


比完小結

今年的題目出的有點詭異,難度跨越有點大

入門 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 龍虎斗

這道題沒話說,只是題目長了點,好好理解一下也是不難的。
我們可以預處理出兩邊陣營的氣勢和(別忘了加上“某一刻天降神兵”)然后枚舉每個兵營,把你的兵加進去,算出之后兩個陣營最終的氣勢,然后選出氣勢之差絕對值最小的哪個陣營就可以了。
C++內置的函數abs由於老是忘掉是什么類型的,所以干脆手打算了。。。
話不多說,直接上代碼(普及- 及以下難度的不用具體講吧?)。還有注意要開 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后才等到一個人,原來等了2分鍾的那個人與司機等得花都謝了,所以這時候肯定是一回來就發車,雖然沒有人剛好到達。

當然,我們先排序。(DP,從排序做起)。

我們用f[i]表示i min 時發一輛車,ps[i]表示1 ~ i 的人數,ts[i]表示1 ~ i 所有人開始等的時間之和。
設上一次發車是jmin時,那么j min及以前的人都已經滾粗了,我們要求j + 1 ~ i所有人等待時間之和。
等待時間之和為Σ(i - t[k]) 可以簡化為 i * 人數 - Σ(t[k]), 人數、Σ(t[k])可以用前綴和來維護(即前面提到的ps、ts數組)。
然后就可以得到轉移方程——

( 0 <= i < m ) f[i] = ps[i] * i - ts[i]
( i > m ) f[i] = min{ f[j] + ( ps[i] - ps[j] ) * i - ( ts[i] - ts[j] ) }( 0 <= j <= i - m )

藍鵝,這樣的復雜度達到了O(MAXT ^ 2)!!!這是遠遠不行的。所以我們要進行優化~

優化I

對於兩個人a、b( ta < tb,b = a + 1) 如果 tb - ta >= 2 * m 可以從中間斷開

如果用work( l, r )表示對l、r區間范圍內進行一次DP work( ls, ta ), ls = tb;

很容易解釋,因為如果兩個人之間時間間隔不小於2m的話,他們是完全可以分兩趟車走的。因為a最遲走的時間為ta + m - 1,車回來的時間為ta + 2m - 1,如果tb >= ta + 2m - 1,剛好可以直接粗發QAQ。(或者理解為b可以作為起點)

優化II

i - 2* m + 1 <= j <= i - m

不難理解,每兩趟車之間間隔不會超過或等於2m(否則中間為什么不再來一趟呢???)

實際上,這已經滿足題目的時間復雜度要求,但是還有一個亂搞優化。(在考場上想到的)

優化III

if ps[i] == ps[i - 1]

​ j = i - m

解釋這個優化要從貪心的角度考慮。

回到原來那個問題:是不是在有人到達時才能發車呢???

前面已經解釋了答案是否定的。這從一個側面告訴我們,如果不是在有人到達時才發車,肯定是由於車來不及回來。

所以,在最優方案中,那趟車一定會在剛好回來時發車。也就是說,i min時車剛好回來,上一次發車是在 i - m min時。

真是玄之又玄。

最終的程序不是在考場中寫的,因為考場中 優化I 采用了路徑壓縮的方法,但是出現一些問題,被卡掉20分。

算法的復雜度也是在O(nm)級別,但常數要比Sooke大佬的代碼大一些。

然后上代碼!(雖然不是最好的解,但87ms也湊合吧。)

#include<bits/stdc++.h>
using namespace std;
#define MAXN 505
#define MAXM 205

int n, m, mm, ans;
int t[MAXN], ps[MAXM * MAXN], ts[MAXM * MAXN];
int f[MAXM * MAXN];

void work( int l, int r ){
	memset( ps, 0, sizeof ps ), memset( ts, 0, sizeof ts ), memset( f, 0, sizeof f );
	for ( int i = l + 1; i <= r; ++i ){
		t[i] -= t[l]; ps[t[i]]++; ts[t[i]] += t[i];
	}
	t[l] = 0; ps[0]++;
	for ( int i = 1; i < t[r] + m; ++i ) ps[i] += ps[i - 1], ts[i] += ts[i - 1];
	for ( int i = 1; i < t[r] + m; ++i ){
		if ( i < m ){ f[i] = ps[i] * i - ts[i]; continue; }
		if ( ps[i] == ps[i - 1] ){ f[i] = f[i - m] + ( ps[i] - ps[i - m] ) * i - ( ts[i] - ts[i - m] ); continue; }
		f[i] = INT_MAX;
		for ( int j = max( 0, i - mm + 1 ); j <= i - m; ++j ) f[i] = min( f[i], f[j] + ( ps[i] - ps[j] ) * i - ( ts[i] - ts[j] ) );
	}
	int cur(INT_MAX);
	for ( int i = t[r]; i < t[r] + m; ++i ) cur = min( cur, f[i] );
	ans += cur;	
}

int main(){
	scanf( "%d%d", &n, &m ); mm = m << 1;
	for ( int i = 1; i <= n; ++i ) scanf( "%d", &t[i] );
	sort( t + 1, t + n + 1 );
	int ls(1);
	for ( int i = 1; i < n; ++i )
		if ( t[i + 1] - t[i] >= mm ) work( ls, i ), ls = i + 1;
	work( ls, n );
	printf( "%d\n", ans );
	return 0;
}

非常抱歉,雖然這個代碼通過了一些民間數據,但CCF的數據實在太強,把我原來的代碼卡到了80分,以上代碼70分,具體錯誤還不清楚,所以就暫時留坑QAQ。
updata 2018/11/30 23:03: 總算填好坑了QAQ

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;
}

鳴謝

感謝葉康傑童鞋的審核。
感謝CCF提供的數據(尤其是把我T3卡掉了的那些QAQ)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM