[CSP-S 2024] 染色

还是决定把这个题做了

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

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

但是考 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';
    }
}
posted @ 2024-11-20 18:14  HaneDaniko  阅读(35)  评论(1编辑  收藏  举报