还是决定把这个题做了
考场上设计的状态,推了一个小时没推出来
下午推了一会,发现这是个刷表状态,填表没法做,转移无处下手
但是考 CSP 的时候我貌似并不知道什么叫刷表
设 \(f_{i,j,k}\) 表示当前到 \(i\),上一个填的红色位置在 \(j\),蓝色位置在 \(k\),暴力刷表转移是 3D/0D 的,需要排除不合法状态,\(j,k\) 里总有一个应该在 \(i-1\) 位置,否则状态就不合法
\(35pts\)
#include<bits/stdc++.h>
using namespace std;
int n;
int a[200001];
int f[102][101][101];
int ans=0;
int main(){
ios::sync_with_stdio(false);
int cases;cin>>cases;while(cases--){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
ans=0;
memset(f,0,sizeof f);
for(int i=1;i<=n;++i){
for(int j=0;j<i;++j){
for(int k=0;k<i;++k){
if(j!=i-1 and k!=i-1) continue;
f[i+1][j][i]=max(f[i+1][j][i],f[i][j][k]+(a[k]==a[i]?a[i]:0));
f[i+1][i][k]=max(f[i+1][i][k],f[i][j][k]+(a[j]==a[i]?a[i]:0));
ans=max({ans,f[i+1][j][i],f[i+1][i][k]});
}
}
}
cout<<ans<<'\n';
}
}
lbtl 说,既然都刷表了,为什么不写记搜呢,一语惊醒梦中人
在写记搜的同时也发现,由于 \(j,k\) 中一定会有一维是 \(i-1\),而另一维一定小于 \(i-1\),因此由 \(j,k\) 可以直接算出 \(i\)(\(i=\max(j,k)+1\)),\(i\) 可省去
记搜 \(50pts\)
#include<bits/stdc++.h>
using namespace std;
int n;
int a[10001];
int f[2001][2001];
int dfs(int now,int r,int b){
if(now>n) return 0;
if(f[r][b]!=-1) return f[r][b];
return f[r][b]=max(dfs(now+1,now,b)+(a[now]==a[r]?a[now]:0),dfs(now+1,r,now)+(a[now]==a[b]?a[now]:0));
}
int main(){
ios::sync_with_stdio(false);
int cases;cin>>cases;while(cases--){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
memset(f,-1,sizeof f);
cout<<dfs(1,0,0)<<'\n';
}
}
刷表法 \(50pts\)
#include<bits/stdc++.h>
using namespace std;
int n;
int a[200001];
int f[2001][2001];
int ans=0;
int main(){
ios::sync_with_stdio(false);
int cases;cin>>cases;while(cases--){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
ans=0;
memset(f,0,sizeof f);
for(int j=0;j<n;++j){
for(int k=0;k<n;++k){
f[j][max(j,k)+1]=max(f[j][max(j,k)+1],f[j][k]+(a[k]==a[max(j,k)+1]?a[max(j,k)+1]:0));
f[max(j,k)+1][k]=max(f[max(j,k)+1][k],f[j][k]+(a[j]==a[max(j,k)+1]?a[max(j,k)+1]:0));
ans=max({ans,f[j][max(j,k)+1],f[max(j,k)+1][k]});
}
}
cout<<ans<<'\n';
}
}
写到这里已经 210 分了,考场上的我到底在干什么呢
这个做法空间压不下来,已经没前途了,想做正解还要另辟蹊径
返璞归真,设 \(f_i\) 表示考虑到第 \(i\) 位的答案
考虑从 \(f_{i-1}\) 转移到 \(f_i\),如果 \(f_i\) 完全没有贡献,那么应该有 \(f_i=f_{i-1}\)
如果 \(f_i\) 有贡献,考虑这个贡献一定是从最大的一个满足 \(j\lt i,a_j=a_i\) 的 \(j\) 转移过来的
证明:为了有贡献,\(j,i\) 中间的数必须全涂另一种颜色,强制涂同一种颜色贡献一定不优,因此需要尽可能减少 \(j,i\) 之间的点的个数
因此,将贡献拆成三段
- \([1,j]\),由 \(f_j\) 可以知道
- \([j+1,i-1]\),这是都涂同一种颜色的连续段
- \(i\),刚才我们钦定 \(i\) 有贡献,这个贡献就是 \(a_i\)
这个东西需要处理细节,分别讨论 \(j,j+1\) 的颜色转移,题解区有一种更高明的分法
- \([1,j+1]\)
- \([j+2,i-1]\)
- \(i\)
这么分是因为,由于离 \(i\) 最近的同值节点是 \(j\),因此 \(a_{j+1}\) 一定与 \(a_i,a_j\) 不相同,我们现在需要保证的是 \(j\) 涂的颜色与 \(i\) 一样,而 \(j+1\) 涂的与 \(i\) 不同,由于 \(j,j+1\) 涂相同的颜色完全没有收益,不存在这样的转移,因此 \(j,j+1\) 颜色一定不同,因此只需要钦定其中一个的颜色即可
现在复杂度瓶颈是第二个连续段,这一段可前缀和处理,因此上前缀和
复杂度线性
这题也有坑点
// if(last[a[i]]) f[i]=max(f[i-1],f[last[a[i]]+1]+sum[i]-sum[last[a[i]]+1]+a[i]);
// else f[i]=f[i-1];
// last[a[i]]=i;
不能这么写是因为 last[a[i]]+1
有可能直接等于 i
了,因此需要提前更新 i
的值
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[1000001];
int last[1000001];
int f[1000001];
int sum[1000001];
signed main(){
ios::sync_with_stdio(false);
int cases;cin>>cases;while(cases--){
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
last[a[i]]=0;
sum[i]=sum[i-1]+(a[i]==a[i-1]?a[i]:0);
}
for(int i=1;i<=n;++i){
f[i]=f[i-1];
if(last[a[i]]) f[i]=max(f[i],f[last[a[i]]+1]+sum[i]-sum[last[a[i]]+1]+a[i]);
last[a[i]]=i;
}
cout<<f[n]<<'\n';
}
}
标签:last,int,染色,sum,cin,2024,cases,now,CSP
From: https://www.cnblogs.com/HaneDaCafe/p/18558481