【题解】2020 年电子科技大学 ACM-ICPC 暑假前集训 - 动态规划

比赛地址

A - 耀西藏蛋

\(f[i][j][k]\)表示从左到右遍历到了第\(i\)列,当前有\(j\)列内一个蛋也没有放,而当前有\(k\)行没有满足右侧埋蛋的需求
\(i-1\)列向第\(i\)列转移时,统计第\(i\)列的:
左区间的右边界行数\(st\)
右区间的左边界行数\(ed\)
不属于左右边界的行数\(mid\)
转移时有三种决策:

  1. 向左区间\([0,i]\)埋入\(st\)个蛋
  2. 在左区间\([0,i-1]\)埋入\(st\)个蛋,同时在第\(i\)列向右区间埋入\(1\)个蛋
  3. 在左区间\([0,i-1]\)埋入\(st\)个蛋,同时在第\(i\)列向中间区间埋入\(1\)个蛋

对三种决策求和即可得到对应状态的方案数
一个比较巧妙的地方是,延迟左区间埋蛋决策的时间,但不提前或延后右区间埋蛋决策的时间
直到扫到区间边界时再决定埋在区间的哪个位置,同时由于边界的单调性,决策不会产生后效性

一开始的想法是把左区间按边界顺序依次决策,但是再怎么处理右区间就不会了
看来还是要学习一个,多找找规律

#include <cstdio>
typedef long long ll;
const ll N=101,M=301,mod=1e9+7;
ll n,m,a[N],b[N],f[M][M][N],ac[M],bc[M],acnt,bcnt,x,y,z,ans;
ll A(ll n,ll m){
    ll t=1;
    for (ll i=n-m+1;i<=n;i++) t=(t*i)%mod;
    return t;
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=1;i<=n;i++){
        scanf("%lld%lld",&a[i],&b[i]);
        b[i]=m+1-b[i];
        ac[a[i]]++;bc[b[i]]++;
    }
    f[0][0][0]=1;
    acnt=n,bcnt=0;
    for (ll i=1;i<=m;i++){
        bcnt+=bc[i];
        for (ll j=0;j<=i;j++)
            for (ll k=0;k<=n;k++){
                x=0,y=0,z=0;
                if (j-ac[i]>=0)
                    x=(f[i-1][j-ac[i]][k]*A(i-(j-ac[i]),ac[i]))%mod;
                if (j-ac[i]-1>=0&&k-1>=0&&bcnt-(k-1)>=0)
                    y=((f[i-1][j-ac[i]-1][k-1]*A((i-1)-(j-ac[i]-1),ac[i]))%mod*(bcnt-(k-1)))%mod;
                if (j-ac[i]-1>=0&&n-acnt-bcnt>=0)
                    z=((f[i-1][j-ac[i]-1][k]*A((i-1)-(j-ac[i]-1),ac[i]))%mod*(n-acnt-bcnt))%mod;
                f[i][j][k]=(x+y+z)%mod;
            }
        acnt-=ac[i];
    }
    ans=0;
    for (ll i=0;i<=m;i++) ans=(ans+f[m][i][n])%mod;
    printf("%lld",ans);
}

B - 黑暗剑22近在咫尺!

C - 赛马

DP按递归顺序,换根
可以证明我们只需要关心包含所有标记节点的最小子树即可
如果出发点不在这个子树内,答案只需要加上从出发点进入这个子树的距离即可
换根DP需要记录,当前点向下走的最长路\(mx\)、向下走第一步和\(mx\)决策不同的最长路\(sec\)
\(mx\)\(sec\)可以得到第一步向上走的最长路\(upmax\)
那么从当前点出发的最长路就是\(max\{mx,upmax\}\)
找一个有标记的节点做根,这样特殊子树外的节点都是靠下的分支,容易进入子树的最短距离
同时,特殊子树内的点如果有父亲,父亲一定也是特殊子树内的点
子树中标记点数量为零,则该点不在特殊子树内

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=100001;
ll n,k,a,b,c,rt,ans,head[N],sz,flag[N],fa[N],dis[N],pre[N];
ll mx[N],maxid[N],sec[N],upmax[N],glmax[N],esum[N],fae[N];
struct E{
    ll next,to,w;
}e[N*2];
void add(ll a,ll b,ll c){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
    e[sz].w=c;
}
void dfs1(ll x){
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to,w=e[i].w;
        if (fa[x]!=v){
            fae[v]=w;
            fa[v]=x;
            dfs1(v);
            flag[x]+=flag[v];
            if (flag[v]&&mx[v]+w>mx[x]){
                mx[x]=mx[v]+w;
                maxid[x]=v;
            }
        }
    }
}
void dfs2(ll x){
    if (flag[x]){
        pre[x]=x;
        dis[x]=0;
    }else{
        pre[x]=pre[fa[x]];
        dis[x]=dis[fa[x]]+fae[x];
    }
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to,w=e[i].w;
        if (fa[x]!=v){
            dfs2(v);
            if (flag[v]){
                esum[x]+=esum[v]+w;
            }
            if (flag[v]&&mx[v]+w>sec[x]&&v!=maxid[x]){
                sec[x]=mx[v]+w;
            }
        }
    }
}
void dfs3(ll x){
    upmax[x]=fae[x]+max(upmax[fa[x]],x==maxid[fa[x]]?sec[fa[x]]:mx[fa[x]]);
    glmax[x]=max(upmax[x],mx[x]);
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to,w=e[i].w;
        if (fa[x]!=v) dfs3(v);
    }
}
int main(){
    scanf("%lld%lld",&n,&k);
    for (ll i=1;i<n;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        add(a,b,c);add(b,a,c);
    }
    for (ll i=1;i<=k;i++){
        scanf("%lld",&a);
        flag[a]=1;
    }
    rt=a;
    dfs1(rt);
    dfs2(rt);
    dfs3(rt);
    for (ll i=1;i<=n;i++){
        ans=dis[i]+esum[rt]*2-glmax[pre[i]];
        printf("%lld\n",ans);
    }
}

D - Gift For You

E - 拱坝老哥喜欢这个

F - 我是音乐小天才

DP按区间长度顺序

#include <cstdio>
#include <iostream>
#include <climits>
using namespace std;
int n,f[5000][5000];
char a[5001];
int main(){
    scanf("%d",&n);
    scanf("%s",a);
    for (int i=2;i<=n;i++)
        for (int j=0;j+i-1<=n-1;j++){
            f[j][j+i-1]=INT_MAX;
            if (a[j]==a[j+i-1]) f[j][j+i-1]=min(f[j][j+i-1],f[j+1][j+i-2]);
            f[j][j+i-1]=min(f[j][j+i-1],f[j][j+i-2]+1);
            f[j][j+i-1]=min(f[j][j+i-1],f[j+1][j+i-1]+1);
        }
    printf("%d",f[0][n-1]);
}

G - 君の名前は?唱:“达拉崩吧斑得贝迪卜多比鲁翁”

DP按物品顺序,二进制优化

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=10001,M=6;
ll n,m,c,v[N],w[N],d[N],x[M],y[M],z[M],f[N],ans,t;
int main(){
    scanf("%lld%lld%lld",&n,&m,&c);
    for (ll i=1;i<=n;i++) scanf("%lld%lld%lld",&v[i],&w[i],&d[i]);
    for (ll i=1;i<=m;i++) scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
    for (ll i=1;i<=n;i++)
        for (ll j=0;j<20&&d[i];j++){
            t=((1<<j)>d[i])?d[i]:(1<<j);
            d[i]-=t;
            for (ll k=c;k>=t*v[i];k--)
                f[k]=max(f[k],f[k-t*v[i]]+t*w[i]);
        }
    for (ll i=1;i<=m;i++)
        for (ll j=c;j>=0;j--)
            for (ll k=0;k<=j;k++)
                f[j]=max(f[j],f[j-k]+x[i]*k*k+y[i]*k+z[i]);
    ans=f[c];
    printf("%lld",ans);
}

H - 无聊的糖哥

DP按加入边权的大小顺序
可以证明最优的情况是边权从\(0\)\(k-1\)连在一条长度为\(k\)的链上
对于大于等于\(k\)的边权无论赋值给哪条边,对答案都没有影响
为了避免处理把两条链拼接成一条链的情况,可以先枚举一个叶子节点作为根
即这个选取的叶子节点拉到最上,其他叶节点垂到最下
DFS时栈维护上到下的区间内的点
记忆化搜索,每个区间搜索一次,一共有\(n^2\)个区间

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const ll N=6001;
ll n,a,b,sta[N],sz,esz,siz[N],head[N],ans,f[N][N],cnt[N];
struct E{
    ll to,next;
}e[N*2];
void add(ll a,ll b){
    esz++;
    e[esz].next=head[a];
    head[a]=esz;
    e[esz].to=b;
}
ll solve(ll l,ll r){
    ll a=sta[l],b=sta[l+1],c=sta[r-1],d=sta[r];
    if (l==r) return 0;
    if (f[a][d]) return f[a][d];
    return f[a][d]=max(solve(l+1,r),solve(l,r-1))+(n-siz[b])*siz[d];
}
void dfs1(ll x){
    sta[++sz]=x;
    siz[x]=1;
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        if (v!=sta[sz-1]){
            dfs1(v);
            siz[x]+=siz[v];
        }
    }
    sz--;
}
void dfs2(ll x){
    sta[++sz]=x;
    if (siz[x]==1) ans=max(ans,solve(1,sz));
    for (ll i=head[x];i;i=e[i].next){
        ll v=e[i].to;
        if (v!=sta[sz-1]){
            dfs2(v);
        }
    }
    sz--;
}
int main(){
    scanf("%lld",&n);
    for (ll i=1;i<n;i++){
        scanf("%lld%lld",&a,&b);
        add(a,b);add(b,a);
        cnt[a]++;cnt[b]++;
    }
    for (ll i=1;i<=n;i++)
        if (cnt[i]==1){
            memset(siz,0,sizeof(siz));
            sz=0;dfs1(i);
            sz=0;dfs2(i);
        }
    printf("%lld",ans);
}

I - 已经没有什么好怕的了!

J - 狂风呼啸,此时站在你面前的是一队真正的特种兵Ⅰ

DP按已经排好的兵种数量顺序,状态压缩

#include <cstdio>
#include <iostream>
using namespace std;
const int N=100001,M=20;
int n,m,a[N],f[1<<M],dp[1<<M],cnt[M],rev,l,r,sum[N][M];
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        a[i]--;
        cnt[a[i]]++;
    }
    for (int i=1;i<=n;i++)
        for (int j=0;j<m;j++)
            sum[i][j]=sum[i-1][j]+(a[i]==j);
    for (int i=0;i<(1<<m);i++)
        for (int j=0;j<m;j++)
            if (i&(1<<j)) f[i]+=cnt[j];
    for (int i=0;i<(1<<m);i++)
        for (int j=0;j<m;j++)
            if (i&(1<<j)){
                l=f[i-(1<<j)]+1,r=f[i];
                dp[i]=max(dp[i],dp[i-(1<<j)]+sum[r][j]-sum[l-1][j]);
            }
    printf("%d",dp[(1<<m)-1]);
}

K - 狂风呼啸,此时站在你面前的是一队真正的特种兵Ⅱ

DP按已经排好的兵种数量顺序,状态压缩

#include <cstdio>
#include <iostream>
#include <climits>
using namespace std;
typedef long long ll;
const ll N=100001,M=20;
ll n,a[N],cnt[M],w[M][M],dp[1<<M],t;
int main(){
    scanf("%lld",&n);
    for (ll i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        a[i]--;
    }
    for (ll i=1;i<=n;i++){
        cnt[a[i]]++;
        for (ll j=0;j<M;j++) w[j][a[i]]+=cnt[j];
    }
    for (ll i=1;i<(1<<M);i++)
    {
        dp[i]=1LL<<62;
        for (ll j=0;j<M;j++)
            if (i&(1<<j)){
                t=0;
                for (ll k=0;k<M;k++)
                    if ((i&(1<<k))&&(j!=k))
                        t+=w[j][k];
                dp[i]=min(dp[i],dp[i-(1<<j)]+t);
            }
    }
    printf("%lld",dp[(1<<M)-1]);
}

L - 保护团长

DP按序列顺序,减少冗余状态,矩阵快速幂
令平衡因子=左括号数-右扩号数
可以证明如果转移中平衡因子超过\(2n\),则可以通过调换使得平衡因子下降的序列在前边,所以平衡因子超过\(2n\)的状态不会对答案所求的最小代价造成影响
\(f[i][j]=min\{f[i-1][j-1]+a[i],f[i-1][j+1]+b[i]\}\)
这个线性的递推式可以使用矩阵乘法进行解决
我们只需要维护边长为\(2n+1\)的矩阵进行快速幂即可

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=21,inf=1LL<<62;
ll n,m,a[N],b[N];
struct Mat{
    ll s[N*2][N*2];
    ll* operator [](ll x){
        return s[x];
    }
    friend Mat operator *(Mat a,Mat b){
        Mat s;
        for (ll i=0;i<=n*2;i++)
            for (ll j=0;j<=n*2;j++)
                s[i][j]=inf;
        for (ll i=0;i<=n*2;i++)
            for (ll j=0;j<=n*2;j++)
                for (ll k=0;k<=n*2;k++)
                    if (a[i][k]!=inf&&b[k][j]!=inf)
                        s[i][j]=min(s[i][j],a[i][k]+b[k][j]);
        return s;
    }
    Mat(){
        for (ll i=0;i<N*2;i++)
            for (ll j=0;j<N*2;j++)
                s[i][j]=inf;
    }
}c[N],sum;
Mat I(){
    Mat s;
    for (ll i=0;i<=n*2;i++)
        for (ll j=0;j<=n*2;j++)
            s[i][i]=(i==j);
    return s;
}
Mat power(Mat a,ll b){
    Mat s=I();
    while(b){
        if (b&1) s=s*a;
        a=a*a;
        b/=2;
    }
    return s;
}
void print(Mat a){
    for (ll i=0;i<=n*2;i++){
        for (ll j=0;j<=n*2;j++)
            printf("%lld ",a[i][j]);
        printf("\n");
    }
    printf("\n");
}
int main(){
    scanf("%lld%lld",&n,&m);
    for (ll i=0;i<n;i++) scanf("%lld",&a[i]);
    for (ll i=0;i<n;i++) scanf("%lld",&b[i]);
    for (ll i=0;i<n;i++)
        for (ll j=0;j<n*2;j++)
            c[i][j][j+1]=a[i],c[i][j+1][j]=b[i];
    sum=I();
    for (ll i=0;i<n;i++) sum=sum*c[i];
    sum=power(sum,m);
    printf("%lld",sum[0][0]);
}

M - 我来变身

N - 猛男修树

DP按树上递归顺序

#include <cstdio>
#include <cstring>
#include <climits>
#include <iostream>
using namespace std;
const int N=201;
int n,p,a,b,sz,head[N],f[N][N],vis[N],ans;
struct E{
    int next,to;
}e[N*2];
void add(int a,int b){
    sz++;
    e[sz].next=head[a];
    head[a]=sz;
    e[sz].to=b;
}
void dfs1(int x){
    vis[x]=1;
    f[x][1]=0;
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!vis[v]){
            dfs1(v);
            f[x][1]++;
        }
    }
}
void dfs2(int x){
    vis[x]=1;
    for (int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if (!vis[v]){
            dfs2(v);
            for (int j=n;j>=1;j--)
                for (int k=1;k<=n;k++)
                    if (j+k<=n)
                    {
                        f[x][j+k]=min(f[x][j+k],f[x][j]+f[v][k]-1);
                    }
        }
    }
}
int main(){
    scanf("%d%d",&n,&p);
    for (int i=1;i<n;i++){
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    memset(f,0x3f,sizeof(f));
    dfs1(1);
    memset(vis,0,sizeof(vis));
    dfs2(1);
    ans=f[1][p];
    for (int i=2;i<=n;i++) ans=min(ans,f[i][p]+1);
    printf("%d",ans);
}

O - 臭数

DP按数位顺序

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath> 
#define ll long long
using namespace std;
const int N=1002,mod=1e9+7;
ll f[N][10][10],w[N],ans=0,pd,lc,rc;
char l[N],r[N],ten[N];
ll count(char *x){
    if (x[0]==0) return 0;
    int cnt=strlen(x);
    for (int i=0;i<cnt;i++) w[cnt-i]=x[i]-'0';
    return cnt;
}
ll check(char *x)
{
    memset(f,0,sizeof(f));
    ll cnt=count(x),s=0;pd=1;
    if (cnt==0) return 0;
    if (cnt==1) return w[cnt];

    for (int j=0;j<=9;j++)
        for (int k=1;k<=9;k++)
            if (j!=k)
                if (k<w[cnt]||(k==w[cnt]&&j<w[cnt-1])) f[cnt-1][j][k]=1;
    if (w[cnt]==w[cnt-1]) pd=0;

    for (int i=cnt-2;i>=1;i--)
    {
        for (int j=0;j<=9;j++)
        {
            for (int k=0;k<=9;k++)
            {
                for (int p=0;p<=9;p++)
                {
                    if (j!=k&&k!=p&&j!=p)
                    {
                        if (k==w[i+1]&&p==w[i+2]&&j<w[i]) f[i][j][k]=(f[i][j][k]+pd)%mod;
                        f[i][j][k]=(f[i][j][k]+f[i+1][k][p])%mod;
                    }
                 }
            }
        }
        if (w[i]==w[i+1]||w[i+1]==w[i+2]||w[i]==w[i+2]) pd=0;
    }
    for (int i=0;i<=9;i++)
        for (int j=0;j<=9;j++)
            s=(s+f[1][i][j])%mod;
    s=(s+pd)%mod;
    return s;
}
int main()
{
    cin>>l>>r;
    for (int i=0;i<strlen(l);i++) lc=(lc*10+l[i]-'0')%mod;
    for (int i=0;i<strlen(r);i++) rc=(rc*10+r[i]-'0')%mod;
    for (int i=0;i<N;i++) ten[i]='9';
    for (int i=count(l);i<=count(r)-1;i++) ans=(ans+check(ten+N-i))%mod;
    ans=(ans+check(r))%mod;
    ans=(ans-check(l)+mod)%mod;
    ans=(ans+pd)%mod;
    cout<<(rc-lc+1-ans+2*mod)%mod<<endl;
}

P - 近战法师暴击好累

DP按区间长度顺序

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int N=501;
int n,w[N],f[N][N],a[N][N];
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&w[i]);
    memset(f,0x7f,sizeof(f));
    for (int i=1;i<=n;i++){
        f[i][i]=1;
        a[i][i]=w[i];
    }
    for (int i=2;i<=n;i++)
        for (int j=1;j+i-1<=n;j++)
            for (int k=j;k<j+i-1;k++){
                if (f[j][k]==1&&f[k+1][j+i-1]==1&&a[j][k]==a[k+1][j+i-1])
                    f[j][j+i-1]=1,a[j][j+i-1]=a[j][k]+1;
                f[j][j+i-1]=min(f[j][j+i-1],f[j][k]+f[k+1][j+i-1]);
            }
    printf("%d",f[1][n]);
}

Q - 藤原货运

DP按时间顺序,单调栈优化

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> P;
const ll N=500001;
ll n,a,b,mn,sz,f[N],pt;
P s[N],p[N],w[N];
struct Stack{
    ll l,r;
    P t[N];
    Stack(){
        l=0,r=1;
    }
    void pop(ll x){
        while(l<=r&&t[l].first<x) l++;
    }
    void push(ll x,ll v){
        while(l<=r&&t[r].second>=v) r--;
        t[++r]=P(x,v);
    }
    bool isempty(){
        return l>r;
    }
    ll bottom(){
        return t[l].second;
    }
}sta;
int main(){
    scanf("%lld",&n);
    for (ll i=1;i<=n;i++){
        scanf("%lld%lld",&a,&b);
        s[i]=P(a,b);
    }
    for (ll i=1;i<=n;i++){
        while(sz&&p[sz].second<=s[i].second) sz--;
        p[++sz]=s[i];
    }
    mn=1LL<<62;
    for (ll i=1;i<=sz;i++){
        a=p[i].first,b=p[i].second;
        f[i]=1LL<<62;
        sta.push(f[i-1],f[i-1]+2*b);
        sta.pop(a);
        if (!sta.isempty()){
            f[i]=min(f[i],sta.bottom());
        }
        w[i]=P(f[i-1],2*b);
        while(pt<=i&&w[i].first<a) mn=min(mn,w[i].second),pt++;
        f[i]=min(f[i],a+mn);
    }
    printf("%lld",f[sz]);
}

R - 君の名前は?唱:“达拉崩吧斑得贝迪卜多比鲁翁” Ⅱ

DP按物品顺序,二进制优化

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const ll N=10001,M=6;
ll n,m,c,v[N],w[N],d[N],x[M],y[M],z[M],f[N],ans,t;
int main(){
    scanf("%lld%lld%lld",&n,&m,&c);
    for (ll i=1;i<=n;i++) scanf("%lld%lld%lld",&v[i],&w[i],&d[i]);
    for (ll i=1;i<=m;i++) scanf("%lld%lld%lld",&x[i],&y[i],&z[i]);
    for (ll i=1;i<=n;i++)
        for (ll j=0;j<20&&d[i];j++){
            t=((1<<j)>d[i])?d[i]:(1<<j);
            d[i]-=t;
            for (ll k=c;k>=t*v[i];k--)
                f[k]=max(f[k],f[k-t*v[i]]+t*w[i]);
        }
    for (ll i=1;i<=m;i++)
        for (ll j=c;j>=0;j--)
            for (ll k=0;k<=j;k++)
                f[j]=max(f[j],f[j-k]+x[i]*k*k+y[i]*k+z[i]);
    ans=f[c];
    printf("%lld",ans);
}
posted @ 2020-06-10 16:27  Byaidu  阅读(191)  评论(0编辑  收藏  举报