題目描述
小D有一包糖果和N張卡片。每張卡片上都有一個正整數Pi。小D想這樣喫糖果,他把兩張卡片用線串起來,如果兩張卡片上的數字分別爲Pa和Pb,他就喫掉min(Pa%Pb,Pb%Pa)的糖果。
他想最終把所有的卡片都串起來——即提起一張卡片,就可以將其他卡片都提起。請問他最少需要喫掉多少糖果。
輸入格式
第一行包含一個正整數N(1≤N≤10^5)
接下來N行,每行一個正整數Pi(1≤Pi≤10^7)
輸出格式
有且只有一行,輸出答案。
輸入樣例
3
4
9
15
輸出樣例
4
題解:最小生成樹
如果兩張卡片上的數相等,那麼他們之間邊權爲0,可以將他們看做一個頂點。於是我們只需要考慮卡片上的互不相等的情況。
設卡片的最大值爲maxz,注意到maxz不超過10^7,將卡片按值由小到大排序,對於某張卡片i,設它的值爲pi。以pi爲週期,將[pi,maxz]分爲若干個區間,最後一個區間可能不足pi。在每個區間找到模pi的值最小的卡片,將該卡片和卡片i連邊。特別的,第一個區間即[pi,2*pi-1],要注意不能找第i張卡片本身。
我們只需要在剛纔連的邊中求一個最小生成樹即可。
此時的邊數最多爲maxz*log(maxz)。
然後注意邊權在10^7以內,所以,我們可以使用計數排序。這樣,時間複雜度爲O(N+maxz*log(maxz))
#include<cstdio> #include<vector> using namespace std; const int N=1e5+10; const int M=1e7; int n, p[N], rot[N], h[N], cnt[M+5]; int Root( int x ) { if( !rot[x] ) return x; return rot[x]=Root( rot[x] ); } int realv[M+5];//realv[i]:記錄原值爲i的位置被修改後的值 int pos[M+5];//pos[i]:記錄i這個值最先出現的標號 struct node{ int s, e; node() {} node( int a, int b ) { s=a; e=b; } }; vector<node> edge[M+5]; long long sum; int main() { scanf( "%d", &n ); for( int i=1; i<=n; i++ ) { scanf( "%d", &p[i] ); realv[ p[i] ]=p[i]; if( !pos[ p[i] ] ) pos[ p[i] ]=i; } for( int i=M; i>=0; i-- ) if( !realv[i] ) realv[i]=realv[i+1]; for( int i=1; i<=n; i++ ) { if( cnt[ p[i] ]++ ) continue; if( realv[ p[i]+1 ] )//特殊處理第一個區間[ p[i], 2*p[i] ) if( realv[ p[i]*2 ]!=realv[p[i]+1] || p[i]*2>M ) edge[ realv[p[i]+1]-p[i] ].push_back( node( i, pos[ realv[p[i]+1] ] ) ); for( int j=p[i]*2; j<=M && realv[j]; j+=p[i] ) if( realv[ j+p[i] ]!=realv[j] || j+p[i]>M ) edge[ realv[j]-j ].push_back( node( i, pos[ realv[j] ] ) ); } for( int i=0; i<=M; i++ ) for( int j=0; j<(int)edge[i].size(); j++ ) { int s=Root( edge[i][j].s ), e=Root( edge[i][j].e ); if( s!=e ) { if( h[s]<h[e] ) rot[s]=e; else rot[e]=s; if( h[s]==h[e] ) h[s]++; sum+=i; } } printf( "%I64d\n", sum ); return 0; }
[NOIP模擬賽]喫糖果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.