DP 砸题1

模拟赛——连续段的价值

定义字母 \(c\) 的连续段为:字符串 \(s\) 中的某个子串,子串的每个字符都为 \(c\)
定义 \(f_i\) 表示第 \(i\) 种字母的所有连续段的最长长度。如果该字母没有出现,则 \(f_i\)\(0\)
蜗蜗有一个长度为 \(n\) 的字符串 \(s\),其中每个字符是前 \(k\) 个小写字母或 ?。他想用前 \(k\) 个小写字母替换 \(s\) 中所有 ?。替换后,蜗蜗想让 \(min_{i=1}^k f_i\) 尽可能大,求最大值。
\(1\le n\le 2\times 10^5, 1\le k\le 17\), \(s\) 仅包含前 \(k\) 个小写字母或者 ?

答案显然具有单调性,所以外面二分答案 \(mid\)

然后 \(f_{i,s}\) 表示前 \(i\) 个位置,已经满足要求字符集合为 \(s\) 的状态是否可行。预处理区间 \(p_{i,c}\)\(i\) 位置向后第一个满足 \(c\) 连续段长度 $\ge mid $ 的位置。复杂度 \(O(2^kn\log n)\)

然后考虑两维状态而 dp 值只有 0/1,所以不妨设 \(f_{s}\) 表示已经满足要求字符集合为 \(s\) 的状态最少需要多长的前缀。枚举 \(s\) 中未出现的数,使 \(f_{s\cup j}\gets p_{f_s,j}\)。复杂度 \((2^kk\log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,k;
char s[N];
int p[N][18],f[1<<18];
int q[N][18];
bool check(int len){
    for(int i=len;i<=n;++i){
        int s=-1,t=0;
        for(int j=0;j<k;++j)
            if(p[i][j]-p[i-len][j])
                s=j,++t;
        if(t==2)continue;
        if(t==1)q[i-len+1][s]=i;
        if(t==0){
            for(int j=0;j<k;++j)
                q[i-len+1][j]=i;
        }
    }for(int j=0;j<k;++j)
        q[n+1][j]=0x3f3f3f3f;
    for(int i=n;i>=1;--i){
        for(int j=0;j<k;++j)
            if(!q[i][j])
                q[i][j]=q[i+1][j];
    }memset(f,0x3f,sizeof(f));
    f[0]=0;
    for(int i=0;i<(1<<k)-1;++i){
        if(f[i]>=n)continue;
        for(int j=0;j<k;++j){
            if((i>>j)&1)continue;
            int v=q[f[i]+1][j];
            f[i|(1<<j)]=min(f[i|(1<<j)],v);
        }
    }for(int i=n;i>=1;--i){
        for(int j=0;j<k;++j)
            q[i][j]=0;
    }return f[(1<<k)-1]<=n;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>k>>s+1;
    for(int i=1;i<=n;++i){
        for(int j=0;j<k;++j)
            p[i][j]=p[i-1][j];
        if(s[i]!='?')++p[i][s[i]-'a'];
    }int l=0,r=(n+k-1)/k,ans=0;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid))ans=mid,l=mid+1;
        else r=mid-1;
    }cout<<ans<<'\n';
    return 0;
}

模拟赛——平面点集划分

二维平面上有 \(n\) 个点,每个点有两个属性值,第 \(i\) 个点的坐标为 \((x_i,y_i)\) 和两个属性值 \(a_i\)\(b_i\)。将这些点划分为两个集合 \(A\)\(B\),满足以下条件:

  • 每个点要么属于集合 \(A\),要么属于集合 \(B\)
  • 不存在集合 \(A\) 中的点 \((x_i,y_i)\) 和集合 \(B\) 中的点 \((x_j,y_j)\) 满足 \(x_i\ge x_j\)\(y_i≤y_j\)(即 \(A\) 中的点不能在 \(B\) 中点的右下方)。
    定义划分的价值为 \((\sum_{i\in A}a_i)+(\sum_{j∈B}b_j)\)
    ,求价值的最大值。
    \(1\le n\le 2\times 10^5,1\le x_i,y_i,a_i,b_i\le 10^9\),保证任意一对 \((x_i,y_i)\) 互不相同。

将点集整体旋转,转化成 \(A\) 中的点不能在 \(B\) 中点的左下方.

然后 A,B 点的分布一定形如下图。

将所有点按 \(x\) 从小到大排,对于 \(x\) 相同的一起考虑。

\(f_{a,b}\) 表示前 \(x\le a\) 的点,放入 \(A\) 集合的点 \(y\) 最小值为 \(b\) 价值最大是多少。

考虑当前这一列将 $y<k $ 的点放到 \(B\) 里面,\(y\ge l\) 的丢进 \(A\)(意味着这一列 \(y\in [k,l-1]\) 没有点)。设其价值为 \(s\),则 \(f_{i,l}\gets f_{i-1,j}+s(j\ge l),f_{i,j}\gets f_{i-1,j}+s(k\le j<l)\)

然后这玩意可以用线段树区间加,区间 \(\max\) 解决。

#include<bits/stdc++.h>
#define mid (l+r>>1)
#define ll long long
using namespace std;
const int N=4e5+5,M=1e9+1;
const ll F=1e15;
namespace SEG{
    int ls[N*2],rs[N*2],rt,tot;
    ll s[N*2],t[N*2];
    void bd(int &p,int l,int r){
        s[p=++tot]=-F;t[p]=0;if(l==r)return;
        bd(ls[p],l,mid);bd(rs[p],mid+1,r);
    }void pd(int p){
        ll T=t[p];t[p]=0;
        s[ls[p]]+=T;s[rs[p]]+=T;
        t[ls[p]]+=T;t[rs[p]]+=T;
    }void upd(int p,int l,int r,int x,ll y){
        if(l==r){s[p]=max(s[p],y);return;}pd(p);
        x<=mid?upd(ls[p],l,mid,x,y):upd(rs[p],mid+1,r,x,y);
        s[p]=max(s[ls[p]],s[rs[p]]);
    }void upd(int p,int l,int r,int L,int R,ll v){
        if(L<=l&&r<=R){s[p]+=v;t[p]+=v;return;}pd(p);
        if(L<=mid)upd(ls[p],l,mid,L,R,v);
        if(mid<R)upd(rs[p],mid+1,r,L,R,v);
        s[p]=max(s[ls[p]],s[rs[p]]);
    }ll ask(int p,int l,int r,int L,int R){
        if(L<=l&&r<=R)return s[p];pd(p);ll res=-F;
        if(L<=mid)res=max(res,ask(ls[p],l,mid,L,R));
        if(mid<R)res=max(res,ask(rs[p],mid+1,r,L,R));
        return res;
    }
}using namespace SEG;
int n,m,k,lsh[N*2];
vector<int> v[N*2];
int sy[N];ll sa[N],sb[N];
struct node{int x,y,a,b;}a[N];
bool cmp(int x,int y){
    return a[x].y<a[y].y;
}void solve(){
    cin>>n;
    for(int i=1;i<=n;++i)
        cin>>a[i].x>>a[i].y>>a[i].a>>a[i].b,
        swap(a[i].x,a[i].y),a[i].y=M-a[i].y,
        lsh[++m]=a[i].x,lsh[++m]=a[i].y;
    sort(lsh+1,lsh+m+1);
    m=unique(lsh+1,lsh+m+1)-lsh-1;
    for(int i=1;i<=n;++i)
        a[i].x=lower_bound(lsh+1,lsh+m+1,a[i].x)-lsh,
        a[i].y=lower_bound(lsh+1,lsh+m+1,a[i].y)-lsh,
        v[a[i].x].push_back(i);
    bd(rt,1,m+1);upd(rt,1,m+1,m+1,0);
    for(int i=1;i<=m;++i){
        if(v[i].empty())continue;k=0;
        sort(v[i].begin(),v[i].end(),cmp);
        for(int o:v[i])
            sy[++k]=a[o].y,
            sa[k]=a[o].a,sb[k]=a[o].b;
        for(int j=1;j<=k;++j)
            sb[j]+=sb[j-1];
        for(int j=k;j>=1;--j)
            sa[j]+=sa[j+1];
        sy[0]=0;sy[k+1]=m+1;
        for(int j=0;j<=k;++j){
            ll t=ask(rt,1,m+1,sy[j+1],m+1);
            upd(rt,1,m+1,sy[j]+1,sy[j+1],sb[j]+sa[j+1]);
            upd(rt,1,m+1,sy[j+1],t+sb[j]+sa[j+1]);
        }
        for(int j=0;j<=k+1;++j)
            sy[j]=sa[j]=sb[j]=0;
        v[i].clear();
    }cout<<s[rt]<<'\n';
    rt=tot=m=0;
}signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;cin>>T;while(T--)solve();
    return 0;
}

模拟赛——安全通道

给定一棵树,问有多少对 \(1\le i<j\le n\),使得添加边 \((i,j)\) 之后其最大独立集不变。

\(f_{i,0/1}\) 表示 \(i\) 的子树中,\(i\) 没选/选的最大独立集大小。然后初始做一遍 dp。

如果添加的边两端中有一个点本来就没选,那么这个方案一定可行。

如果两端都有,不妨以其中一个端点为根 \(rt\),做一遍 dp,判断 \(f_{rt,0}\) 是否等于答案。

那么我们可以拆贡献。如果一个点 \(u\) 为根时 \(f_{u,0}\) 等于答案,那么它与其他所有点连边时答案都不变。

设这样的点有 \(k\) 个,最终答案为 \(k(n-k)+\frac {k(k-1)}2\)

对于求解 \(k\) 是一个简单换根。

#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N=2.5e5+5;
int n,f[N][2],mx,k;
vector<int> e[N];
void dfs1(int u,int fa){
    f[u][1]=1;
    for(int v:e[u]){
        if(v==fa)continue;
        dfs1(v,u);
        f[u][1]+=f[v][0];
        f[u][0]+=max(f[v][1],f[v][0]);
    }
}void dfs2(int u,int fa){
    if(f[u][0]==mx)
        ++k;
    for(int v:e[u]){
        if(v==fa)continue;
        int F1=f[u][1]-f[v][0];
        int F0=f[u][0]-max(f[v][1],f[v][0]);
        f[v][1]+=F0;
        f[v][0]+=max(F0,F1);
        dfs2(v,u);
    }
}signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1,u,v;i<n;++i)
        cin>>u>>v,e[u].eb(v),e[v].eb(u);
    dfs1(1,0);
    mx=max(f[1][0],f[1][1]);
    dfs2(1,0);
    cout<<1ll*k*(n-k)+1ll*k*(k-1)/2<<'\n';
    return 0;
}

CF1874C Jellyfish and EVA

考虑倒着做 dp,\(f_i\) 表示
\(i\)\(n\) 的成功概率最大值是多少。

考虑从 \(u\) 能到达 \(v_1,v_2,\cdots v_k\),根据一点贪心原理,我们肯定优先去成功概率高的地方。不妨令 \(g_{i,j}\) 表示有 \(i\) 个可能的走向,成功走向成功率第 \(j\) 大位置的概率。

g[i][j]
k=1 1
k=2 1/2 0
k=3 1/3 1/3 1/3
k=4 1/4 

我们肯定优先选第一个数,设明日香选了 \(j\)。若 \(j=1\) 则成功,概率为 \(\frac 1n\),否则位置 \(j\) 将不可达,此时我们转化成了一个 \(n-2\) 规模的问题,只是新加一个 \(\frac 1n\) 的系数。

爆求 \(g\)\(O(n^3)\) 的,随手推一下就发现 \(g_{i,j}\)\(g_{i-2,j-1}\)\(g_{i-2,j-2}\) 乘以一个系数相加,所以可以做到 \(O(n^2)\)

倒着 dp 时,将可达点按 \(f\) 从大到小排序,按顺序乘上 \(g\) 即可。

#include<bits/stdc++.h>
#define pb push_back
#define db double
using namespace std;
const int N=5005;
int n,m,k;
db f[N],g[N][N],h[N];
vector<int> e[N];
void init(){
    const int n=5000;
    g[1][1]=1;g[2][1]=0.5;
    for(int i=3;i<=n;++i){
        g[i][1]=1.0/i;
        for(int j=2;j<=i;++j)
            g[i][j]=(g[i-2][j-1]*(i-j)+g[i-2][j-2]*(j-2))/i;
    }
}void solve(){
    cin>>n>>m;
    for(int i=1,x,y;i<=m;++i)
        cin>>x>>y,e[x].pb(y);
    f[n]=1;
    for(int i=n-1;i>=1;--i){
        for(int v:e[i])
            h[++k]=f[v];
        sort(h+1,h+k+1,greater<db>());
        for(int j=1;j<=k;++j)
            f[i]+=h[j]*g[k][j];
        k=0;
    }cout<<fixed<<setprecision(10)<<f[1]<<'\n';
    for(int i=1;i<=n;++i)
        f[i]=0,e[i].clear();
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
    init();
    int T;cin>>T;while(T--)solve();
    return 0;
}

CF780F Axel and Marston in Bitland

0 01 0110 01101001 等为正路径,1 10 1001 10010110 等为反路径。

这玩意一看就跟倍增有关系。令 \(f_{u,v,k,0/1}\)\(u\)\(v\) 长度为 \(2^k\) 的正/反路径是否存在。

\(f_{u,v,k,0/1}=\lor_{w=1}^n f_{u,w,k-1,0/1}\lor f_{w,v,k-1,1/0}\)

这样玩复杂度是 \(O(n^3\log V)\) 的,可以用 bitset 优化一下就行。

计算答案时,再开一个 bitset 表示目前对于每个点是否可达。贪心考虑每一位,如果按位与之后非零,则可以走。

#include<bits/stdc++.h>
using namespace std;
const int N=503;
int n,m;long long ans;
bitset<N> bt[2][61][N],f,g;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;++i)
        cin>>u>>v>>w,bt[w][0][u][v]=1;
    for(int k=0;k<60;++k){
        for(int u=1;u<=n;++u)
            for(int w=1;w<=n;++w){
                if(bt[0][k][u][w])
                    bt[0][k+1][u]|=bt[1][k][w];
                if(bt[1][k][u][w])
                    bt[1][k+1][u]|=bt[0][k][w];
            }
    }g[1]=1;
    for(int i=60,p=0;i>=0;--i){
        f.reset();
        for(int u=1;u<=n;++u)
            if(g[u])f|=bt[p][i][u];
        if(f.count())g=f,p^=1,ans|=(1ll<<i);
    }cout<<(ans>1e18?-1:ans)<<'\n';
    return 0;
}

CF1557D Ezzat and Grid

\(f_i\) 表示前 \(i\) 行,强制保留第 \(i\) 行,最少删多少行。枚举上一个保留的位置,只要两行有交集就可以转移,即 \(f_i\gets f_j+i-j\)

既然只要有交集就可以转移,我们将 \(f_i+i\) 的值放到所有第 \(i\) 行所有黑的位置,和这里取 \(\min\)

然后需要一棵区间取 \(\min\),区间求 \(\min\) 的线段树。

#include<bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define eb emplace_back
#define mk make_pair
#define mid (l+r>>1)
#define fi first
#define se second
using namespace std;
const int N=3e5+5,M=2e6+5,F=1e9;
namespace SEG{
    int ls[N*4],rs[N*4],rt,tot;
    pii s[N*4],t[N*4];
    void Min(pii &x,pii y){
        if(y<x)x=y;
    }void bd(int &p,int l,int r){
        p=++tot;
        t[p]=s[p]=mk(F,0);
        if(l==r)return;
        bd(ls[p],l,mid),bd(rs[p],mid+1,r);
    }void pd(int p){
        pii T=t[p];t[p]=mk(F,0);
        Min(s[ls[p]],T);Min(s[rs[p]],T);
        Min(t[ls[p]],T);Min(t[rs[p]],T);
    }void upd(int p,int l,int r,int L,int R,pii v){
        if(L<=l&&r<=R)return Min(s[p],v),Min(t[p],v);pd(p);
        if(L<=mid)upd(ls[p],l,mid,L,R,v);
        if(mid<R)upd(rs[p],mid+1,r,L,R,v);
        s[p]=min(s[ls[p]],s[rs[p]]);
    }pii ask(int p,int l,int r,int L,int R){
        if(L<=l&&r<=R)return s[p];pd(p);pii res=mk(F,0);
        if(L<=mid)Min(res,ask(ls[p],l,mid,L,R));
        if(mid<R)Min(res,ask(rs[p],mid+1,r,L,R));
        return res;
    }
}using namespace SEG;
int n,m,ans=1e9;
int las[N],lsh[N*2],cnt;
vector<pii> v[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
    for(int i=1,x,l,r;i<=m;++i)
        cin>>x>>l>>r,v[x].eb(mk(l,r)),
        lsh[++cnt]=l,lsh[++cnt]=r;
    sort(lsh+1,lsh+cnt+1);
    cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
    bd(rt,1,cnt);
    for(int i=1;i<=n;++i){
        int tmp=i-1;las[i]=0;
        for(auto x:v[i]){
            int l=lower_bound(lsh+1,lsh+cnt+1,x.fi)-lsh;
            int r=lower_bound(lsh+1,lsh+cnt+1,x.se)-lsh;
            pii tem=ask(rt,1,cnt,l,r);
            if(tem.fi+i-1<tmp)
                tmp=tem.fi+i-1,las[i]=tem.se;
        }
        if(tmp+n-i<ans)
            ans=tmp+n-i,las[n+1]=i;
        for(auto x:v[i]){
            int l=lower_bound(lsh+1,lsh+cnt+1,x.fi)-lsh;
            int r=lower_bound(lsh+1,lsh+cnt+1,x.se)-lsh;
            upd(rt,1,cnt,l,r,mk(tmp-i,i));
        }
    }cout<<ans<<'\n';
    for(int i=n+1;i;i=las[i])
        for(int j=i-1;j>las[i];--j)
            cout<<j<<' ';
    return 0;
}
posted @ 2025-08-18 08:16  zzy0618  阅读(27)  评论(0)    收藏  举报