1. 題目
題目鏈接:P2418「yyy loves OI IV」 。
題目背景
某校2015屆有兩位OI神牛,yyy和c01。
題目描述
全校除他們以外的N名學生,每人都會膜拜他們中的某一個人。現在老師要給他們分宿舍了。但是,問題來了:
同一間宿舍裏的人要麼膜拜同一位大牛,要麼膜拜yyy和c01的人數的差的絕對值不超過M。否則他們就會打起來。
爲了方便,老師讓N名學生站成一排,只有連續地站在一起的人才能分進同一個宿舍。
假設每間宿舍能容納任意多的人,請問最少要安排幾個宿舍?
輸入格式
第一行,兩個正整數N和M。
第2……N+1行,每行一個整數1或2,第i行的數字表示從左往右數第i-1個人膜拜的大牛。
1表示yyy,2表示c01。
輸出格式
一行,一個整數,表示最少要安排幾個宿舍。
輸入輸出樣例
輸入 #1
5 1
1
1
2
2
1
輸出 #1
1
說明/提示
難度題,做好心理準備~
測試點編號 | N的範圍 | M的範圍 |
---|---|---|
1~3 | <=2,500 | <=10 |
4~5 | <=500,000 | <=10 |
6~10 | <=500,000 | <=2,000 |
2. 題解
分析
從第一個同學開始,逐步往最後一個同學掃描;易知除了最後一個宿舍,其餘宿舍:
- 要麼全部膜拜同一個大佬
- 要麼二者數量之差的絕對值等於 M
設 dp[i] 表示處理完前 i 個同學至少需要的宿舍數量,stu[i] 表示第 i 個同學的膜拜的大佬(-1 代表 1, 1 代表 2),sum[i] = stu[i] 表示前 i 個同學膜拜的大佬的代數數量和(即膜拜兩位大佬的數量之差,這便是將 1 和 2 映射爲 -1 和 1 的原因:方便處理),ump[u] 表示對於當前輸入,膜拜者二者數量相差絕對值爲 u 時所需的最少宿舍數量(由於 u 的取值範圍較大,故選擇 unordered_map 來實現存儲記錄)。
【注】可以設定 ump[u] 的原因在於,最優情況下,所有宿舍出現的情況是一樣的,即膜拜一者的同學大於等於膜拜另一者的同學且膜拜數量較大的一者都是一樣的(不然由於宿舍數量沒有上限,則可以合併相鄰膜拜者數量較大的一者不同的宿舍,仍然滿足題意條件)。
對於第一種情況:可以通過設定記錄上一個不同記錄值的位置來實現;即 dp[i] <= dp[last] + 1 ,其中 last 爲與當前 stu[i] 膜拜大佬相對的上一個同學的位置。即處理到當前同學,最多比上一個道不同的同學的宿舍數量多一(因爲此時兩者之間的同學膜拜的都是同一個大佬)。
對於第二種情況:dp[i] <= ump[abs(sum[i]) - M] + 1 ,即至少小於等於比現在少 M 個數量差的情況下所需最少宿舍數量 + 1 。
因此,對於 u <= 0 而言,ump[u] = 0 。因爲 abs(sum[i]) - M <= 0 說明此時膜拜二者的數量差還沒有超過 M ,因此此時最多需要 1 個宿舍,即 ump[abs(sum[i]) - M] = 0 。
綜合二者,則可以列出狀態轉移方程:
dp[i] = min(dp[last]+1, ump[abs(sum[i]) - M] + 1)。最終結果即爲 dp[n] 。
代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
int stu[MAXN], sum[MAXN], dp[MAXN];
//終極快讀
char buf[1<<22],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
//快讀
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
void input(int n) {
for(int i = 1; i <= n; ++i) {
int a = read();
stu[i] = 2*a - 3;
}
}
int myabs(int x) {
return x < 0 ? -x : x;
}
void init(int n) {
for(int i = 1; i <= n; ++i) {
sum[i] = sum[i-1] + stu[i];
}
}
unordered_map <int,int> ump;
void update(int u, int x) {
if(u > 0) {
if(ump.count(u) == 0) {
ump[u] = x;
} else {
ump[u] = min(ump[u], x);
}
}
}
int query(int u) {
if(ump.count(u) == 0) {
if(u <= 0) {
return 0;
} else {
return MAXN;
}
}
return ump[u];
}
int answer(int n, int m) {
int last1 = 0, last2 = 0;
for(int i = 1; i <= n; ++i) {
if(stu[i] == 1) {
dp[i] = dp[last2] + 1;
last1 = i;
} else {
dp[i] = dp[last1] + 1;
last2 = i;
}
dp[i] = min(dp[i], query(myabs(sum[i])-m)+1);
update(myabs(sum[i]), dp[i]);
}
return dp[n];
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
input(n);
init(n);
printf("%d\n", answer(n,m));
return 0;
}