[CSP-S 2024] 染色

还是决定把这个题做了

考场上设计的状态,推了一个小时没推出来

下午推了一会,发现这是个刷表状态,填表没法做,转移无处下手

但是考 CSP 的时候我貌似并不知道什么叫刷表


fi,j,k 表示当前到 i,上一个填的红色位置在 j,蓝色位置在 k,暴力刷表转移是 3D/0D 的,需要排除不合法状态,j,k 里总有一个应该在 i1 位置,否则状态就不合法

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 中一定会有一维是 i1,而另一维一定小于 i1,因此由 j,k 可以直接算出 ii=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 分了,考场上的我到底在干什么呢

这个做法空间压不下来,已经没前途了,想做正解还要另辟蹊径


返璞归真,设 fi 表示考虑到第 i 位的答案

考虑从 fi1 转移到 fi,如果 fi 完全没有贡献,那么应该有 fi=fi1

如果 fi 有贡献,考虑这个贡献一定是从最大的一个满足 j<i,aj=aij 转移过来的

证明:为了有贡献,j,i 中间的数必须全涂另一种颜色,强制涂同一种颜色贡献一定不优,因此需要尽可能减少 j,i 之间的点的个数

因此,将贡献拆成三段

  • [1,j],由 fj 可以知道
  • [j+1,i1],这是都涂同一种颜色的连续段
  • i,刚才我们钦定 i 有贡献,这个贡献就是 ai

这个东西需要处理细节,分别讨论 j,j+1 的颜色转移,题解区有一种更高明的分法

  • [1,j+1]
  • [j+2,i1]
  • i

这么分是因为,由于离 i 最近的同值节点是 j,因此 aj+1 一定与 ai,aj 不相同,我们现在需要保证的是 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';
    }
}
posted @   HaneDaniko  阅读(37)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示