題意
https://codeforces.com/problemset/problem/45/G
作爲數論題,這個題面給出的問題情景可以打高分,所以把題目簡述下
有個房子,編號,現要給它們塗色,要求是:
- 一幢房子塗一種顏色,塗同種顏色的房子編號之和應該是素數
- 顏色種數不能太多,要儘量少
- 不同顏色的房子不能連續在一起
輸入樣例
8
輸出樣例
1 2 2 1 1 1 1 2 //塗色1的房子編號之和是1+4+5+6+7=23,塗色2的房子編號之和是2+3+8=13,都是素數
或者
1 1 1 1 2 1 1 1
算法:構造、數論
題中在條件2的限制性,可以忽略條件3,因爲若有很多種顏色的房子,肯定不符合條件2的限制。
由條件出發很難想到用什麼數論算法來解決,但是研究樣例可以發現這個“哥德巴赫猜想”——任意一個偶數可以拆成兩個素數之和有關,只不過題目已知的數不一定是偶數,因此需要分類討論:
假設,那麼:
- 若是素數,那麼所有房子直接塗1色
- 若不是素數,那麼它有可能是奇數或偶數:
- 如果是偶數,那麼只要2種顏色即可:用歌德巴赫猜想枚舉第一個素數,然後那所房子塗2,其餘都塗1
- 如果是奇數,那麼可能是素數,若是也只要2種顏色,除2號房子是2外,其餘都塗1
- 可能也不是素數,那麼至少需要3種顏色,此時可以有兩種算法:
- 算法1:直接將3號房子塗3,然後是偶數,按照哥德巴赫方式處理
- 算法2:對於這個值,從大到小枚舉小於的素數,這個素數對應的房子可以塗1,而餘下部分肯定是偶數,按哥德巴赫方式處理
以下按算法1實現。對於求素數,暴力、埃氏篩、線性篩都可以。實際素數判定次數很少,最大值用暴力應該也可以過。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
const int maxn = 6002 * 3000;
int prime[maxn], flag[maxn], a[6002], cnt = 0;
void get_prime()
{
for (int i = 2; i < maxn; i++)
{
if (!flag[i])
{
prime[cnt++] = i;
//flag[i] = true;
}
for (int j = 0; j < cnt && i * prime[j] < maxn; j++)
{
flag[prime[j] * i] = true;
if (i % prime[j] == 0)
break;
}
}
}
void goldbach(int s)
{
for (int i = 2; (i << 1) <= s; i++) // 嚴格講,這裏應該循環到(i <= s / 2 )
{
if (!flag[i] && !flag[s-i])
{
a[i] = 2;
return;
}
}
}
int main()
{
freopen("demo.in", "r", stdin);
freopen("demo.out", "w", stdout);
int n, s;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
a[i] = 1;
s = (n * (n+1)) >> 1;
get_prime();
if (flag[s])
{
if (s % 2 == 0)
{
// 偶數,2組
goldbach(s);
} else if (!flag[s-2]) {
// s-2是素數
a[2] = 2;
} else {
// s是奇數,s-2也不是素數,那麼至少3組
a[3] = 3;
goldbach(s-3);
}
}
for (int i = 1; i <= n; i++)
printf("%d%s", a[i], i == n ? "\n" : " ");
return 0;
}