喫糖果(candy)
【題目描述】
小D有一包糖果和N張卡片。每張卡片上都有一個正整數Pi。小D想這樣喫糖果,他把兩張卡片用線串起來,如果兩張卡片上的數字分別爲Pa和Pb,他就喫掉min(Pa%Pb,Pb%Pa)的糖果。
他想最終把所有的卡片都串起來——即提起一張卡片,就可以將其他卡片都提起。請問他最少需要喫掉多少糖果。
【輸入格式】
第一行包含一個正整數N(1<=N<=105)
接下來N行,每行一個正整數Pi(1<=Pi<=107)
【輸出格式】
有且只有一行,輸出答案。
【數據範圍】
30%的數據N<=103
40%的數據Pi<=106
70%的數據上述條件至少滿足1個。
【輸入樣例1】
4
2
6
3
11
【輸出樣例1】
1
【輸入樣例2】
4
1
2
3
4
【輸出樣例2】
0
【輸入樣例3】
3
4
9
15
【輸出樣例3】
4
對於兩個點u,v它們邊的長度就是min(Pu%Pv,Pv%Pu),
如果兩張卡片上的數相等,那麼他們之間邊權爲0,可以將他們看做一個頂點。
於是我們只需要考慮卡片上的互不相等的情況。
設卡片的最大值爲maxz,注意到maxz不超過10^7,將卡片按值由小到大排序,對於某張卡片i,設它的值爲pi。
以pi爲週期,將[pi,maxz]分爲若干個區間,最後一個區間可能不足pi。
在每個區間找到模pi的值最小的卡片,將該卡片和卡片i連邊,特別的,第一個區間即[pi,2*pi-1],要注意不能找第i張卡片本身。
我們只需要在剛纔連的邊中求一個最小生成樹即可。
aft[P[i]]表示值大於P[i]的第一個值。
下面給出一些思考點和證明:
1,爲什麼第一段要單獨處理?
因爲每段區間mod最小的值應該儲存在aft[P[i] * x]的地方,但是第一段不能選P[i],所以必須向後移一個成爲aft[P[i] + 1]
2,爲什麼每個P[i]值只對它處理一遍?
因爲V的下標是邊的長度,而每個相同的點權之間可以連邊使得邊權爲0,所以只處理一遍,就是將那一堆權值相同的點中選出一個和其他權值不同的點相連。
3,區間中的點爲什麼選擇aft邊界位置中的最優?
因爲對於P[i]來說,倍數肯定是mod值最小的,而不是倍數的話,就考慮離倍數最近的一個爲當前區間中的最優值。
4,爲什麼從選出來的這些邊中做最小生成樹一定能保證N個點都連通?
注意我們的循環是對於每一個i而言的,所有點權不同的i一定都會被枚舉到,並且每個點i都至少會在大小爲P[i]的區間中找到一個與之相匹配的點來連邊,最後的並查集一定會訪問到V數組中所有的點,並保證將它們連成一棵最小生成樹。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<vector>
#include<cassert>
#include<algorithm>
using namespace std;
const int Max = 1 << 19;
const int Maxm = 1e7;
struct node{
int u, v;
node(){}
node(int a, int b){u = a, v = b;}
};
int N;
int P[Max + 5], fa[Max + 5], rnk[Max + 5];
int pos[Maxm + 5], aft[Maxm + 5], Cnt[Maxm + 5];
vector<node>V[Maxm + 5];
bool getint(int & num){
char c; int flg = 1; num = 0;
while((c = getchar()) < '0' || c > '9'){
if(c == '-') flg = -1;
if(c == -1) return 0;
}
while(c >= '0' && c <= '9'){
num = num * 10 + c - 48;
if((c = getchar()) == -1) return 0;
}
num *= flg;
return 1;
}
int root(int x){
if(! fa[x]) return x;
return fa[x] = root(fa[x]);
}
int main(){
freopen("candy.in", "r", stdin);
freopen("candy.out", "w", stdout);
getint(N);
for(int i = 1; i <= N; ++ i){
getint(P[i]);
aft[P[i]] = P[i];
if(pos[P[i]] == 0)
pos[P[i]] = i;
}
for(int i = Maxm - 1; i >= 0; -- i) if(! aft[i])
aft[i] = aft[i + 1];
for(int i = 1; i <= N; ++ i){
if(Cnt[P[i]] ++) continue;
if(aft[P[i] + 1]){
if(P[i] * 2 > Maxm || aft[2 * P[i]] != aft[P[i] + 1])
V[aft[P[i] + 1] - P[i]].push_back(node(i, pos[aft[P[i] + 1]]));
}
for(int j = 2 * P[i]; j <= Maxm && aft[j]; j += P[i])
if(j + P[i] > Maxm || aft[j + P[i]] != aft[j])
V[aft[j] - j].push_back(node(i, pos[aft[j]]));
}
long long rt = 0LL;
for(int i = 0; i <= Maxm; ++ i)
for(int j = 0; j < V[i].size(); ++ j){
int a = root(V[i][j].u), b = root(V[i][j].v);
if(a != b){
if(rnk[a] > rnk[b]) fa[b] = a;
else if(rnk[a] < rnk[b]) fa[a] = b;
else fa[a] = b, ++ rnk[a];
rt += i;
}
}
printf("%lld\n", rt);
return 0;
}