2022.3.27 总结

CF82D Two out of Three
d p i , j dp_{i,j} dpi,j 表示第 i i i轮, j j j未选得到的最大值
p r e i , j , ( 0 / 1 / 2 ) pre_{i,j,(0/1/2)} prei,j,(0/1/2) 中 :
p r e i , j , 0 / 1 pre_{i,j,0/1} prei,j,0/1表示在 d p i , j dp_{i,j} dpi,j满足最大值时,这一轮选择的数的下标
p r e i , j , 2 pre_{i,j,2} prei,j,2表示 d p i , j dp_{i,j} dpi,j满足最大值时,前一轮剩下的数的下标

i i i轮从三个下标选数: j , 2 ∗ i , 2 ∗ i + 1 j,2*i,2*i+1 j,2i,2i+1 方程很好转移,pre记录决策
如果 n n n为奇数,只需要枚举第 ⌊ \lfloor n 2 \frac{n}{2} 2n ⌋ \rfloor 轮最后没选的数 加上即可
如果 n n n为偶数,只需要在最后随便加一个数,最后答案就是第 ⌊ \lfloor n 2 \frac{n}{2} 2n ⌋ \rfloor
没选第 n + 1 n+1 n+1个数

// LUOGU_RID: 106060092
#include 
#define ll long long
using namespace std;
int pre[1005][1005][3];
int n;

void prin(int tol,int bad){
    if (!tol){
        return;
    }
    prin(tol-1,pre[tol][bad][2]);
    cout<<pre[tol][bad][0]<<" "<<pre[tol][bad][1]<<"\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    
    cin>>n;
    vector<int> a;
    a.push_back(0);
    for (int i=1;i<=n;i++){
        int x;
        cin>>x;
        a.push_back(x);
    }

    if (n==1){
        cout<<a[1]<<"\n"<<1;
        return 0;
    }else if (n==2){
        cout<<max(a[1],a[2])<<"\n"<<"1 2";
        return 0;
    }

    if(n%2==0){
        a.push_back(1e8);
    }

    vector<vector<int>> dp(n/2+5,vector<int>(n+5,1e9+1));
    dp[1][1]=max(a[2],a[3]);pre[1][1][0]=2;pre[1][1][1]=3;pre[1][1][2]=1;
    dp[1][2]=max(a[1],a[3]);pre[1][2][0]=1;pre[1][2][1]=3;pre[1][2][2]=2;
    dp[1][3]=max(a[1],a[2]);pre[1][3][0]=1;pre[1][3][1]=2;pre[1][3][2]=3;


    for (int i=2;i<=n/2;i++){
        for (int j=1;j<2*i;j++){
            // cout<
            if(dp[i][j]>dp[i-1][j]+max(a[2*i],a[2*i+1])){
                dp[i][j]=dp[i-1][j]+max(a[2*i],a[2*i+1]);
                pre[i][j][0]=2*i;
                pre[i][j][1]=2*i+1;
                pre[i][j][2]=j;
            }
            if (dp[i][2*i]>dp[i-1][j]+max(a[2*i+1],a[j])){
                dp[i][2*i]=dp[i-1][j]+max(a[2*i+1],a[j]);
                pre[i][2*i][0]=j;
                pre[i][2*i][1]=2*i+1;
                pre[i][2*i][2]=j;
            }
            if (dp[i][2*i+1]>dp[i-1][j]+max(a[2*i],a[j])){
                dp[i][2*i+1]=dp[i-1][j]+max(a[2*i],a[j]);
                pre[i][2*i+1][0]=j;
                pre[i][2*i+1][1]=2*i;
                pre[i][2*i+1][2]=j;
            }
        }
    }

    if (n%2==0){
        cout<<dp[n/2][n+1]<<"\n";
        prin(n/2,n+1);
    }else{
        int pos=1;
        for (int i=2;i<=n;i++){
            if (dp[n/2][pos]+a[pos]>dp[n/2][i]+a[i]){
                pos=i;
            }
        }
        cout<<dp[n/2][pos]+a[pos]<<"\n";
        prin(n/2,pos);
        cout<<pos;
    }
    return 0;
}

CF847E Packmen
二分答案+贪心, c h e c k check check方法就是先把左边没跳完的跳完,再尽可能往右跳

#include 
#define ll long long
using namespace std;
int main(){
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    // cout.tie(0);
    
    int n;
    cin>>n;
    vector<char> s(n+1);
    vector<int> pos,now;
    for (int i=1;i<=n;i++){
        cin>>s[i];
        if (s[i]=='P') pos.push_back(i);
        if (s[i]=='*') now.push_back(i);
    }

    int l=0,r=2e5,ans;

    function<bool(int)> check=[&](int x){
        int poss=0,noww=0;
        while (poss<pos.size()){
            int need=0,time=x;
            // cout<
            if (pos[poss]>now[noww]){
                need=pos[poss]-now[noww];
                // cout<
            }

            if (need>time){
                return 0;
            }

            int maxn=max(time-need*2,(time-need)/2);
            while (now[noww]<=pos[poss]+maxn&&noww<now.size()){
                noww++;
            }
            if (noww==now.size()){
                return 1;
            }
            poss++;
        }
        return 0;
    };
    // cout<<"kkk\n";
    while (l<=r){
        int mid=(l+r)/2;
        if (check(mid)){
            ans=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }

    cout<<ans<<"\n";
    return 0;
}

CF855C Helga Hufflepuff’s Cup
树上染色 d p i , j , ( 0 / 1 / 2 ) dp_{i,j,(0/1/2)} dpi,j,(0/1/2)表示以 i i i为根的子树,选取了 j j j个特殊颜色的点, i i i节点颜色为 0:小于特殊颜色 k k k,1:等于特殊颜色 k k k,2:大于特殊颜色 k k k 的方案数

对于一个以 u u u节点为根的子树,考虑对其一个一个加孩子,因为满足乘法原理,所以加入一个新的孩子,就让之前累乘完的 u u u节点状态来进行转移

d p u , j + k , 0 dp_{u,j+k,0} dpu,j+k,0+= d p u , j , 0 ∗ ( d p v , k , 0 + d p v , k , 1 + d p v , k , 2 ) dp_{u,j,0}*(dp_{v,k,0}+dp_{v,k,1}+dp_{v,k,2}) dpu,j,0(dpv,k,0+dpv,k,1+dpv,k,2)
d p u , j + k , 1 dp_{u,j+k,1} dpu,j+k,1+= d p u , j , 1 ∗ d p v , k , 0 dp_{u,j,1}*dp_{v,k,0} dpu,j,1dpv,k,0
d p u , j + k , 2 dp_{u,j+k,2} dpu,j+k,2+= d p u , j , 2 ∗ ( d p v , k , 0 + d p v , k , 2 ) dp_{u,j,2}*(dp_{v,k,0}+dp_{v,k,2}) dpu,j,2(dpv,k,0+dpv,k,2)

但是在转移到 d p u , j + k , ( 0 / 1 / 2 ) dp_{u,j+k,(0/1/2)} dpu,j+k,(0/1/2)时,等号右边有关 d p u , j , ( 0 / 1 / 2 ) dp_{u,j,(0/1/2)} dpu,j,(0/1/2)的值会改变,所以我们先用一个辅助数组 g j + k , ( 0 / 1 / 2 ) g_{j+k,(0/1/2)} gj+k,(0/1/2)来储存转移后的状态
再加入一个孩子节点的转移完成后,再将 f f f的值赋给 d p dp dp

// LUOGU_RID: 106079875
#include 
#define ll long long
using namespace std;
const ll mod=1e9+7;

int n,m,K,x;
int siz[100005];
ll f[100005][12][3];
ll g[12][3];
vector<vector<int>> e(100005);

void dfs(int u,int fa){
    siz[u]=1;
    f[u][0][0]=K-1;
    f[u][1][1]=1;
    f[u][0][2]=m-K;


    for (auto v:e[u]){
        if (v==fa) continue;
        dfs(v,u);
        memset(g,0,sizeof(g));
        for (int j=0;j<=min(siz[u],x);j++){
            for (int k=0;k<=min(siz[v],x)&&k+j<=x;k++){
                g[j+k][1]=(g[j+k][1]+f[u][j][1]*f[v][k][0]%mod)%mod;
                g[j+k][0]=(g[j+k][0]+(f[v][k][1]+f[v][k][0]+f[v][k][2])%mod*f[u][j][0]%mod)%mod;
                g[j+k][2]=(g[j+k][2]+(f[v][k][0]+f[v][k][2])%mod*f[u][j][2]%mod)%mod;
            }
        }
        
        siz[u]+=siz[v];
        for (int j=0;j<=min(siz[u],x);j++){
            f[u][j][1]=g[j][1];
            f[u][j][2]=g[j][2];
            f[u][j][0]=g[j][0];
        }
        
    }
}
    

int main(){

    cin>>n>>m;
    for (int i=1;i<n;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    cin>>K>>x;
    dfs(1,-1);

    ll ans=0;
    for (int j=0;j<=x;j++){
        for (int k=0;k<=2;k++){
            // cout<
            ans=(ans+f[1][j][k])%mod;
        }
    }
    cout<<ans;
    return 0;
}

CF222E Decoding Genome
转移很简单,由题意合法转移即可,但 n n n很大,由于转移是一个线性的,所以考虑构造成矩阵,用矩阵乘法 快速幂加速来做
对于构造的 m ∗ m m*m mm矩阵只需让 a c h a r 2 , c h a r 1 = 0 a_{char2,char1}=0 achar2,char1=0 即可

// LUOGU_RID: 106099368
#include 
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll n,a[55][55],ans[55],s[55][55],temp[55][55];
int m;

void oper1(){
    memset(temp,0,sizeof(temp));
    for (int i=1;i<=m;i++){
        for (int j=1;j<=m;j++){
            for (int k=1;k<=m;k++){
                temp[i][j]=(temp[i][j]+a[i][k]*s[k][j]%mod)%mod;
            }
        }
    }

    for (int i=1;i<=m;i++){
        for (int j=1;j<=m;j++){
            a[i][j]=temp[i][j];
        }
    }
}

void oper2(){
    memset(temp,0,sizeof(temp));
    for (int i=1;i<=m;i++){
        for (int j=1;j<=m;j++){
            for (int k=1;k<=m;k++){
                temp[i][j]=(temp[i][j]+s[i][k]*s[k][j]%mod)%mod;
            }
        }
    }

    for (int i=1;i<=m;i++){
        for (int j=1;j<=m;j++){
            s[i][j]=temp[i][j];
        }
    }
}

void quickm(){
    ll temp=n;

    for (int i=1;i<=m;i++){
        a[i][i]=1;
    }

    while (temp){
        if (temp&1){
            oper1();
        }
        oper2();
        temp>>=1;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int q;
    cin>>n>>m>>q;

    for (int i=1;i<=m;i++){
        for (int j=1;j<=m;j++){
            s[i][j]=1;
        }
    }

    while (q--){
        char x,y;
        cin>>x>>y;
        int xx,yy;
        if (x>='a'&&x<='z') xx=x-'a'+1;
        else xx=x-'A'+27;
        if (y<='Z'&&y>='A') yy=y-'A'+27;
        else yy=y-'a'+1;
        s[yy][xx]=0;
    }


    for (int i=1;i<=m;i++){
        ans[i]=1;
    }
    n--;

    quickm();

    memset(temp,0,sizeof(temp));
    for (int j=1;j<=m;j++){
        for (int k=1;k<=m;k++){
            temp[1][j]=(temp[1][j]+ans[k]*a[k][j]%mod)%mod;
        }
    }

    ll sum=0;
    for (int i=1;i<=m;i++){
        sum=(sum+temp[1][i])%mod;
    }
    cout<<sum<<"\n";
    return 0;
}

CF533B Work Group
简单的树形dp方案统计
d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) 表示以 i i i为根的子树,在没选 u u u时,选择的子树个数为0偶/1奇
偶由奇加奇 偶加偶转移 奇由奇加偶 偶加奇转移即可

// LUOGU_RID: 106109430
#include 
#define ll long long
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int n;
    cin>>n;
    vector<vector<int>> e(n+1);
    vector<ll> a(n+1);
    for (int i=1;i<=n;i++){
        int b;
        cin>>b>>a[i];
        if (b>0) e[b].push_back(i);
    }


    vector<vector<ll>> dp(n+1,vector<ll>(2));
    function<void(int)> dfs=[&](int u){
        dp[u][1]=-1e18;

        for (auto v:e[u]){
            dfs(v);
            ll x=dp[u][0],y=dp[u][1];
            dp[u][0]=max(dp[v][0]+x,dp[v][1]+y);
            dp[u][1]=max(dp[v][1]+x,dp[v][0]+y);
        }
        dp[u][1]=max(dp[u][1],dp[u][0]+a[u]);
        
    };

    dfs(1);
    cout<<max(dp[1][0],dp[1][1]);
    return 0;
}

CF1201D Treasure Hunting
离谱题,贪心一层到下一层,要想最快把下一层的宝藏捡完,最终一定会停在最左/最右的宝藏处。

所以每层只需设两个状态, d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) i i i层,0最后在左端点,1最后在右端点的最短总时间花费

贪心 假设当前宝藏全捡完,所在的最左/最右端点的所在列不是安全的列
那么就往左/右找到最靠近的安全列向上走到下一层,贪心易证(如果当前列就是安全列,那么也可以合并在这种情况中,详见代码)
找最靠近的安全列用二分来查找

那么转移的情况就是:

  1. 上一列最左端点转移到下一列最左端点或最右端点
  2. 上一列最右端点转移到下一列最左端点或最右端点

最后还需要特殊处理空行的情况,空行应该在上一行的基础上总时间花费+1
我的思路是提前储存第 i i i行前:第一个非空行是 b e f o r e before before 那么 d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) 就由 d p b e f o r e , ( 0 / 1 ) dp_{before,(0/1)} dpbefore,(0/1) 转移

可以用函数来简化形式类似的转移方程,方便调试。

// LUOGU_RID: 106145466
    #include 
    #define ll long long
    using namespace std;
    int main(){
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        
        int n,m,K,q;
        cin>>n>>m>>K>>q;
        vector<ll> l(n+1,m+1),r(n+1);
        while (K--){
            ll x,y;
            cin>>x>>y;
            l[x]=min(l[x],y);
            r[x]=max(r[x],y);
        }
        while (r[n]==0&&n){
            n--;
        }

        vector<ll> use;
        for (int i=0;i<q;i++){
            int x;
            cin>>x;
            use.push_back(x);
        }

        sort(use.begin(),use.end());

        int before=1;
        vector<vector<ll>> dp(n+1,vector<ll>(2,1e18));
        if (r[1]){
            dp[1][0]=r[1]-1+(r[1]-l[1]);
            dp[1][1]=r[1]-1;    
        }else{
            dp[1][0]=dp[1][1]=use[0]-1;
            l[1]=r[1]=use[0];
        }
        
        for (int i=2;i<=n;i++){
            //从上一行左端点转移
            int pos1=lower_bound(use.begin(),use.end(),l[before])-use.begin();
            if (use[pos1]!=l[before]&&pos1) pos1--;
            int pos2=lower_bound(use.begin(),use.end(),l[before])-use.begin();
            if (use[pos2]<l[before]) pos2=use.size()-1;

            ll x=llabs(l[before]-use[pos1]),y=llabs(use[pos2]-l[before]);
            if (!r[i]){
            }else{
                if (use[pos1]<=l[i]){
                     dp[i][0]=min(dp[i][0],dp[before][0]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
                     dp[i][1]=min(dp[i][1],dp[before][0]+x+(r[i]-use[pos1])+i-before);
                }else if (use[pos1]>=r[i]){
                    dp[i][0]=min(dp[i][0],dp[before][0]+x+(use[pos1]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][0]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
                }else{
                    dp[i][0]=min(dp[i][0],dp[before][0]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][0]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
                }

                if (use[pos2]<=l[i]){
                    dp[i][0]=min(dp[i][0],dp[before][0]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][0]+y+(r[i]-use[pos2])+i-before);
                }else if (use[pos2]>=r[i]){
                    dp[i][0]=min(dp[i][0],dp[before][0]+y+(use[pos2]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][0]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
                }else{
                    dp[i][0]=min(dp[i][0],dp[before][0]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][0]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
                }
            }


            //右端点
            pos1=lower_bound(use.begin(),use.end(),r[before])-use.begin();
            if (use[pos1]!=r[before]&&pos1)pos1--;
            pos2=lower_bound(use.begin(),use.end(),r[before])-use.begin();
            if (use[pos2]<r[before]) pos2=use.size()-1;


            x=llabs(r[before]-use[pos1]);
            y=llabs(use[pos2]-r[before]);
            // if (n>=8&&i==6)cout<
            if (!r[i]){
            }else{
                
                if (use[pos1]<=l[i]){
                     dp[i][0]=min(dp[i][0],dp[before][1]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
                     dp[i][1]=min(dp[i][1],dp[before][1]+x+(r[i]-use[pos1])+i-before);
                }else if (use[pos1]>=r[i]){
                    dp[i][0]=min(dp[i][0],dp[before][1]+x+(use[pos1]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][1]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
                }else{
                    dp[i][0]=min(dp[i][0],dp[before][1]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][1]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
                }

                if (use[pos2]<=l[i]){
                    dp[i][0]=min(dp[i][0],dp[before][1]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][1]+y+(r[i]-use[pos2])+i-before);
                }else if (use[pos2]>=r[i]){
                    dp[i][0]=min(dp[i][0],dp[before][1]+y+(use[pos2]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][1]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
                }else{
                    dp[i][0]=min(dp[i][0],dp[before][1]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
                    dp[i][1]=min(dp[i][1],dp[before][1]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
                }
                before=i;
            }
        }
        // if (n>=8)cout<
        cout<<min(dp[n][0],dp[n][1]);
        return 0;
    }

总结 :3.27总计做了4道dp

一道二分答案加贪心(这个贪心方法跟周天vp的一场2020ICPC青岛站的E很像E. Plants vs. Zombies )

两道树形dp结合计数原理,其中CF533B Work Group较常规,CF855C Helga Hufflepuff’s Cup这一道学会了多用一个数组辅助转移(防止当前状态下dp的值发生变化)

一道很板子的矩阵加速优化dpCF222E Decoding Genome

还有一道CF1201D Treasure Hunting 很多细节,需要大胆贪心,严谨处理实现的dp题(有数据结构那味了) ,最大收获就是对于很特殊的情况不要偷懒,分类然后不要偷懒,重新用不同的方法实现,不要偷懒,不要偷懒,不要偷懒

你可能感兴趣的:(codeforces,算法)