染色:傻逼題。
賽時沒切染色的都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!都是唐氏!
包括我。
真的太傻逼了這題。
我今晚心血來潮一打這題,隨便最佳化一下,就 AC 了。
怎麼做到這麼蠢的啊我。
暴力 dp
不要從子串的角度去考慮,我們對每一個數進行考慮 dp,否則會和我在場上一樣想成用區間設計 dp,並且只能打出 \(O(n^4)\) 的暴力出來。
設計 \(dp_{i}\) 表示考慮到第 \(i\) 個數的最大值。
這裡不用設計兩種顏色的狀態,因為他們本質上都是一樣的,我們分段時只注重前一段的開頭的前一個數,僅此而已。
那麼很顯然就可以打個 \(n^2\) 的暴力了吧?然後 \(50pts\) 到手,然後我考場上沒想出來,太傻逼了。
值域最佳化
顯然,我們不用列舉前一段的開頭的前一個數在哪,只需要知道它的值是什麼就好了。這樣就能根據字首取個最大值進行 dp 了。
設計 \(dp_{i,v}\) 表示考慮到第 \(i\) 個數,本段區間的開頭的前一個數是 \(v\) 時的最大答案。為啥是本段呢,因為下一段轉移的時候,這裡的本段就成了前一段。
於是顯然就維護一個線段樹轉移一下就好了啊。
有兩種轉移,第一個是同色轉移,第二個是不同色轉移。
同色轉移
條件:\(a_i=a_{i-1}\)。
我們只要給線段樹上每一個點加上 \(a_i\) 即可。
異色轉移
條件:無。
顯然的轉移:
就是要麼這一段開頭與前一段開頭的前一個數不相等,挑一個最大值轉移,要麼就是相等,加上貢獻即可,注意由於之前進行過同色轉移,要減掉這一部分的貢獻。(可以證明同色轉移之後一定是最大的,因為畢竟都把之前所有值的最大值加了 \(a_i\) 嘛)
時間 \(O(t n \log V)\),有機率被卡。
陣列最佳化
觀察到只有全域性最大值,全域性加,單點修改操作,於是直接把線段樹換成陣列省去一個老哥即可。
全域性加直接打懶標記就好。
由於懶標記一視同仁,所以比較加懶標記之前的 dp 值轉移,輸出時加上懶標記即可。
時間 \(O(tV)\),單組資料時間複雜度瓶頸在於初始化。
程式碼
/*
定義 dp_i,0/1 表示第 i 個數染色為 1 或 0 時的最大得分。
我們分別維護每種元素在考慮到第 i 個元素時充當上一段段尾的最大得分。
和上個數同色時:
當 i=i-1 時,此時前面的數里面全部加該點的點權。
否則不做任何改動。
這個數作為新開頭:
修改上一個元素在 dp 裡的值。
轉移是 dp_{i-1}=max(allmax,a_i+dp_{i}-[i==i-1]*a[i])
*/
#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pi;
ll n,a[200005],dp[1000005],add,mx=0;
void solve()
{
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
memset(dp,-0x3f,sizeof(dp));
add=0;
mx=0;
for(int i=1;i<=n;i++)
{
if(a[i]==a[i-1])add+=a[i];
dp[a[i-1]]=max(mx,a[i]+dp[a[i]]-(a[i]==a[i-1])*a[i]);
mx=max(mx,dp[a[i-1]]);
}
cout<<mx+add<<'\n';
}
int main()
{
//freopen("color.in","r",stdin);
//freopen("color.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)solve();
return 0;
}