动态规划专题 - 解题报告

 

 https://acm.uestc.edu.cn/contest/15/summary/?tdsourcetag=s_pctim_aiomsg

dp专题要刷完!!

 A - oy环游世界 - 解题报告

状态压缩dp入门题

注意要开long long

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[1<<18][18];
ll e[20][20];
ll x[20],y[20];
const ll INF=1e18;
int main()
{
    int n,s;
    scanf("%d%d",&n,&s);
    int tot=0;
    for(int i=1; i<=n; i++)
    {
        ll a,b;
        scanf("%lld%lld",&a,&b);
        if(s==i)
        {
            x[n-1]=a;
            y[n-1]=b;
        }
        else
        {
            x[tot]=a;
            y[tot]=b;
            tot++;
        }
    }
    for(int i=0; i<tot; i++)
        for(int j=0; j<tot; j++)
        {
            e[i][j]=abs(x[i]-x[j])+abs(y[i]-y[j]);
        }
    for(int s=0;s<(1<<tot);s++)
        for(int i=0;i<tot;i++)
            dp[s][i]=INF;
    for(int i=0;i<tot;i++)
        dp[1<<i][i]=abs(x[i]-x[n-1])+abs(y[i]-y[n-1]);
    for(int s=0;s<(1<<tot);s++)
        for(int i=0;i<tot;i++)
            for(int j=0;j<tot;j++)
            {
                if(i!=j&&((1<<i)&s)&&(((1<<j)&s)==0))
                {
                    dp[s|1<<j][j]=min(dp[s|1<<j][j],dp[s][i]+e[i][j]);
                }
            }
    ll ans=INF;
    for(int i=0;i<tot;i++)
    {
        ans=min(ans,dp[(1<<tot)-1][i]);
//        printf("%d\n",dp[(1<<tot)-1][i]);
    }
    printf("%lld",ans);
}
View Code

B - 挖矿攻略 - 解题报告

棋盘dp问题

日哦,没说矿道不能拐弯?只能直西直北?浪费时间!

#include<bits/stdc++.h>
using namespace std;

int a[505][505];
int b[505][505];
int xi[505][505];
int bei[505][505];
int f[505][505];
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)&&n)
    {
        memset(f,0,sizeof(f));
        memset(xi,0,sizeof(xi));
        memset(bei,0,sizeof(bei));
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
            {
                scanf("%d",&a[i][j]);
                xi[i][j]+=xi[i][j-1]+a[i][j];
            }
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
            {
                scanf("%d",&b[i][j]);
                bei[i][j]+=bei[i-1][j]+b[i][j];
            }
        for(int i=1; i<=n; i++)
            for(int j=1; j<=m; j++)
                f[i][j]=max(xi[i][j]+f[i-1][j],bei[i][j]+f[i][j-1]);
        printf("%d\n",f[n][m]);
    }
}
View Code

C - 手办 - 解题报告

四位数组DP好题,找了好久BUG,这道题要优化,时间空间上都需要。

思路:状态f[i][j][k][l],考虑了i个物品,取了j次,状压当前书架上剩余的高度状态k,此时书架最后一本书高度j,的最小混乱度。

状态转移:

a[i]==l,一定不取,f[i][j][k][l]=min(f[i][j][k][l],f[i-1][j][k][l])

a[i]!=l,取第i个物品f[i][j+1][k][l]=f[i-1][j][k][l],不取的话f[i][j][新的k][a[i]]=f[i-1][j][k][a[i]]+1

之后枚举i,j,k,l得到的混乱度加上该状态下未出现的高度,求最小值。

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
int f[2][105][1<<8+1][9];// 如果第一维105会达到1e9超内存,需要滚动数组优化
int a[105];
int v[10];
int num[1<<9+10];

int init(int x)
{
    int ret = 0;
    while(x)
    {
        if(x&1)
            ret++;
        x >>= 1;
    }
    return ret;
}

int main()
{
    int it=0;
    int n,m,now;
    for (int i = 0; i <= 1<<8; i++)
        num[i] = init(i);
    while(~scanf("%d%d",&n,&m)&&n+m)
    {
        int tot=0; // tot记录所有出现的高度个数 
        int all=0; // all这里用来记录出现的最大状态 
        memset(v,0,sizeof(v));
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            a[i]-=114514;
            all|=(1<<a[i]);
            if(v[a[i]]==0)
            {
                tot++;
                v[a[i]]=1;
            }
        }
        memset(f[0],INF,sizeof(f[0]));
        f[0][0][0][8]=0;
        for(int i=1; i<=n; i++)
        {
            now=i%2;
            memset(f[now],INF,sizeof(f[now]));
            for(int j=0; j<=min(m,i); j++)
                for(int s=0; s<=all; s++)
                    for(int k=0; k<=8; k++)
                    {
                        if(f[!now][j][s][k]==INF) continue;
                        if(k==a[i]) f[now][j][s][k]=min(f[now][j][s][k],f[!now][j][s][k]);
                        else
                        {
                            f[now][j][s|(1<<a[i])][a[i]]=min(f[now][j][s|(1<<a[i])][a[i]],f[!now][j][s][k]+1);
                            f[now][j+1][s][k]=min(f[now][j+1][s][k],f[!now][j][s][k]);
                        }
                    }
        }
        int ans=INF;
        for(int j=0; j<=m; j++)
            for(int s=0; s<=all; s++)
                for(int k=0; k<=8; k++)
                {
                    if(f[now][j][s][k]!=INF)
                        ans=min(ans,f[now][j][s][k]+tot-num[s]); // 所有出现的高度tot-状态含的num[s]就是未出现的高度 
                }
        printf("Case %d: %d\n\n",++it,ans);
    }
}
View Code

D - 序列 - 解题报告

最长上升路径nlogn做法

正反求一次到每个数字且末尾数字为该数字的最长上升子序列,然后枚举每个端点作为中间值。注意!初始化box为负无穷,注意数据范围

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e6+10;
#define ll long long
int lt[maxn],rt[maxn];
int box[maxn];
int a[maxn];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
        scanf("%d",&a[i]);
    int len=0;
    box[0]=-1e9-10;
    for(int i=1; i<=n; i++)
    {
        if(a[i]>box[len])
        {
            box[++len]=a[i];
            lt[i]=len;
        }
        else
        {
            int pos=lower_bound(box+1,box+1+len,a[i])-box;
            box[pos]=a[i];
            lt[i]=pos;
        }

    }
    len=0;
    memset(box,0,sizeof(box));
    box[0]=-1e9-10;
    for(int i=n; i>=1; i--)
    {
        if(a[i]>box[len])
        {
            box[++len]=a[i];
            rt[i]=len;
        }
        else
        {
            int pos=lower_bound(box+1,box+1+len,a[i])-box;
            box[pos]=a[i];
            rt[i]=pos;
        }
    }
    ll ans=0;
    for(int i=1; i<=n; i++)
        ans=max(ans,1ll*min(lt[i],rt[i])*2-1);
    printf("%lld",ans);
}
View Code

E - 神 - 解题报告

线性DP,设f[i][j]表示,到第i块中j的位置作为最后一个字符的最小划分数。

状态转移:枚举i块所有位置j作为最后一个位置,再枚举i-1块中所有位置k作为最后一个位置,如果i-1块中字母在i中出现,同时满足s[j]!=s[k]或者如果s[j]==s[k]&&cnt=1,cnt是i块中字母种类数,那么这个i的块数产生的划分数可以为cnt-1,否则只能是cnt。

#include<bits/stdc++.h>
using namespace std;

int f[1005][1005];
char s[1005];
int v[30];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int k;
        scanf("%d%s",&k,s+1);
        int len=strlen(s+1);
        int duan=len/k;
        int cnt=0;
        memset(v,0,sizeof(v));
        for(int i=1; i<=k; i++)
        {
            if(v[s[i]-'a'+1]==0)
                cnt++;
            v[s[i]-'a'+1]=1;
        }
        memset(f,0x3f,sizeof(f));
        for(int i=1; i<=k; i++)
            f[1][i]=cnt;
        for(int i=2; i<=duan; i++)
        {
            memset(v,0,sizeof(v));
            cnt=0;
            for(int j=(i-1)*k+1; j<=i*k; j++)
            {
                if(v[s[j]-'a'+1]==0)
                    cnt++;
                v[s[j]-'a'+1]=1;
            }
            for(int j=1; j<=k; j++)
            {
                for(int kk=1; kk<=k; kk++)
                {
                    int jt=(i-1)*k+j;
                    int jk=(i-2)*k+kk;
                    if(v[s[jk]-'a'+1]==1&&(cnt==1||s[jt]!=s[jk]))
                        f[i][j]=min(f[i][j],f[i-1][kk]+cnt-1);
                    else
                        f[i][j]=min(f[i][j],f[i-1][kk]+cnt);
                }
            }
//            for(int j=1;j<=k;j++)
//                printf("%d ",f[i][j]);
//            printf("\n");
        }
        int ans=0x3f3f3f3f;
        for(int i=1;i<=k;i++)
            ans=min(ans,f[duan][i]);
        printf("%d\n",ans);
    }
}
View Code

F - 苇名欧一郎 - 解题报告

状压dp,做完这道题感觉对状压的DP又加深了理解。

思路:f[1<<16]状压杀死的人,与A题类似,每个状态下枚举可以杀的敌人,如果可以f[t]+=f[s]s为目前状态,t为加了这个可以杀的人的状态。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll f[1<<16];
char a[20][20];

int main()
{
    int t,n;
    scanf("%d",&t);
    for(int it=1; it<=t; it++)
    {
        scanf("%d",&n);
        scanf("%s",a[n]);
        for(int i=0; i<=n-1; i++)
            scanf("%s",a[i]);
        memset(f,0,sizeof(f));
        f[0]=1;
        for(int s=0; s<1<<n; s++)
        {
            for(int i=0; i<n; i++)
            {
                if((s&(1<<i))==0)
                {
                    int flag=0;
                    if(a[n][i]=='1') flag=1;
                    for(int j=0; j<n; j++)
                    {
                        if(s&(1<<j)&&a[j][i]=='1')
                            flag=1;
                        if(flag) break;
                    }
                    if(flag) f[s|1<<i]+=f[s];
                }
            }
        }
        printf("Case %d: %lld\n",it,f[(1<<n)-1]);
    }
}
View Code

G - 子串 - 解题报告

这道题需要降维或者滚动数组优化,我选择了滚动数组,这样比较好理解好写hh

思路:设f[i][j],以第i位为最低位的字符串%k余j的个数。这里需要注意只能从高位往低位滚动,这样余数*10就是加上新的数的余数,而如果从低位往高位就搞不定了呀。

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e7+10;
#define ll long long
ll f[2][maxn];
char a[maxn];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    scanf("%s",a+1);
    int now;
    ll ans=0;
    for(int i=1; i<=n; i++)
    {
        now=i%2;
        for(int j=0;j<=k;j++)
            f[now][j]=0;
        int t=(a[i]-'0');
        for(int j=0; j<=k; j++)
        {
            int tt=(t+j*10)%k;
            f[now][tt]+=f[!now][j];
        }
        t=t%k;
        f[now][t]++;
        ans+=f[now][0];
    }
    printf("%lld",ans);
}
View Code

H - van游戏 - 解题报告

I - 攻略妹纸 - 解题报告

变形的01背包问题

思路:一开始可以想到,可以枚举自身的上限魅力值,也就是给妹子的最大花费,之后就是一个01背包O(N3),但可以想到如果我们给妹子所需最大魅力值从大到小排序,以每个妹子的所需魅力值作为最大魅力值花费的上限来枚举,同时这样排过序之后,不同魅力值之间的f[][]是不会相互覆盖的,可以优化为O(N2)。

 

#include<bits/stdc++.h>
using namespace std;

const int maxn=6005;
#define ll long long

ll f[maxn];
ll n,m,k,x,y,ans;
ll a,b,c,d;
struct note
{
    ll val,cost,goal;
} g[maxn];
int cmp(note a,note b)
{
    return a.goal>b.goal;
}
int main()
{
    scanf("%lld%lld%lld%lld%lld",&n,&m,&k,&x,&y); // x氪自己 y氪妹子
    for(int i=1; i<=n; i++)
    {
        scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
        g[i].val=a;
        // 你对她好感a,她对你好感b,c妹子对你好感要求c,你魅力要求d
        if(k<d)
            g[i].goal=m-(d-k)*x;
        else
            g[i].goal=m;
        if(c>b)
            g[i].cost=(c-b)*y;
        else
            g[i].cost=0;
    }
    sort(g+1,g+1+n,cmp);
    for(int i=1; i<=n; i++)
    {
        for(int j=g[i].goal; j>=g[i].cost; j--)
            f[j]=max(f[j],f[j-g[i].cost]+g[i].val);
        ans=max(ans,f[g[i].goal]);
    }
    printf("%lld",ans);
}
View Code

 

 

 

K - 抽卡 - 解题报告

L - zh的奖杯 - 解题报告

M - 崭新龙狙,制霸全区 - 解题报告

 

posted @ 2019-08-29 20:57  paranoid。  阅读(270)  评论(0编辑  收藏  举报