[NOIP模擬賽]喫糖果

題目描述
小D有一包糖果和N張卡片。每張卡片上都有一個正整數Pi。小D想這樣喫糖果,他把兩張卡片用線串起來,如果兩張卡片上的數字分別爲PaPb,他就喫掉min(Pa%Pb,Pb%Pa)的糖果。
他想最終把所有的卡片都串起來——即提起一張卡片,就可以將其他卡片都提起。請問他最少需要喫掉多少糖果。


輸入格式

第一行包含一個正整數N(1≤N10^5)
接下來N行,每行一個正整數Pi1Pi10^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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章