2017.3.25校内赛(balance)(hill)(draw)(knight)

动态规划的专练,由远在上海的CKY学长命题。总体而言,题目还是有难度。

天平(balance.in/balance.out)

物理老师YJ有一个长杆天平,天平的两臂长均为15,将长杆看作x轴,则平衡点在0位置处,负数位置在左臂上,正数位置在右臂上。长杆上有n个位置有挂钩可以挂秤砣。YJ有m个秤砣,质量分别为gi,每个挂钩可以不挂也可以挂任意个秤砣。YJ想要知道,在使用所有秤砣的条件下,有多少种不同的挂秤砣的方案,可以使得天平平衡?问题太过复杂,仅凭物理知识难以解决,所以请你来帮助他。
天平的平衡条件是所有秤砣的位置质量之和为0。例如有质量为2,3,4的秤砣分别挂在-3,-2,3位置处,则2(-3) + 3*(-2) + 4*3 == 0,天平是平衡的。
【输入格式】
第一行两个数n,m。表示挂钩的数目和秤砣的数目。
第二行n个不同且递增的数,第i个数表示第i个挂钩的位置,数的范围在[-15,15]内。
第三行m个不同且递增的数,第i个数表示第i个秤砣的质量,数的范围在[1,25]内。
【输出格式】
一个整数,代表能使得天平平衡的方案数。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=20;
const int N=15000;
int n,m,tot;
long long c[M+5],map[M+5],f[M+5][N+5];//放了多少东西,背包大小 
int main()
{
    freopen("balance.in","r",stdin);
    freopen("balance.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%I64d",&map[i]);
    for(int i=1;i<=m;i++)
    scanf("%I64d",&c[i]);
    f[0][7500]=1;
    for(int i=1;i<=m;i++)
    for(int k=1;k<=n;k++)//两个相当于一个 
    for(int j=0;j<=N;j++)//背包大小 
    {
        if(j-map[k]*c[i]<0||j-map[k]*c[i]>N) continue;//不超界 
        f[i][j]+=f[i-1][j-map[k]*c[i]];
    }
    printf("%I64d",f[m][7500]);//正好不剩 
    return 0;
}
方法比较简单。

考虑天平的状态可以用秤砣质量距离来表示,而题目情况下,天平的全部秤砣质量距离在(-7500,7500)(20*25*15 = 7500)以内。故可以以此状态来dp。
类似背包问题,把每一个秤砣挂在不同地方,对应成n*m个物品,转移方程:f[i][j]+=f[i-1][j-dis[k]*mg[i]] (i=1..n,j=0..15000,k=1..m)

山峰数(hill.in/hill.out)

山峰数是指数字排列中不存在山谷(先降后升)的数,例如0,5,13,12321都是山峰数,101,1110000111都不是山峰数。
现给出n个数,请依次判断它们是否为山峰数,如果不是,输出-1。如果是,求出比它小的数中有多少个山峰数。
【输入格式】
第一行一个数n,表示询问数目。
接下来n行,每一行一个数x,表示询问的数。
【输出格式】
输出有n行,x如果不是山峰数,输出-1。x如果是山峰数,则输出有多少个比它小的山峰数。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<algorithm>
#include<iostream>
#define ms(x,y) memset(x,y,sizeof(x))
#define ll long long
using namespace std;

ll f[75][10][2][2];
int len,a[75];
char str[105];

ll dfs(int p,int nu,int isdown,int lim) {
    if(p==len+1)return 1;
    if(f[p][nu][isdown][lim]!=-1)return f[p][nu][isdown][lim];
    ll tmpans=0;
    int r=lim?a[p]:9;
    for(int i=0;i<=r;i++) {
        if(!isdown){
            if(i>=nu) tmpans+=dfs(p+1,i,0,lim && i==r);
            else tmpans+=dfs(p+1,i,1,lim && i==r);
        }
        else if (i<=nu) tmpans+=dfs(p+1,i,1,lim && i==r);
    }
    return f[p][nu][isdown][lim]=tmpans;
}
int main()
{
    freopen("hill.in","r",stdin);
    freopen("hill.out","w",stdout);
    int tc;
    scanf("%d\n",&tc);
    while(tc--){
        scanf("%s",str);
        len=strlen(str);
        for(int i=0;i<len;i++)
            a[i+1]=str[i]-'0';

        bool isdown=false;
        bool ishill=true;
        for(int i=2;i<=len;i++){
            if(a[i]<a[i-1])
                isdown=true;
            if(isdown && a[i]>a[i-1]){
                printf("-1");
                ishill=false;
                break;
            }
        }
        if(ishill) {
            ms(f,-1);
            cout<<dfs(1,0,0,1)-1;
        }
        if(tc!=0)
            printf("\n");
    }
    return 0;
}
数位dp

模板基础上,加入参数nu和isdown,nu表示上一个数填的多少,isdown表示当前是在上升期还是在下降期,然后使用模板记忆化搜索即可。

粉刷匠2(draw.in/draw.out)

有一个4*N的木板需要粉刷,第i行j列的颜色记为A(i, j)。
有256种颜色,记为0..255,为了使得粉刷比较好看,粉刷需要满足如下要求:
1. A(x,y) >= A(x,y-1)
2. 有一些指定的(x1, y1)和(x2, y2),要求A(x1, y1) = A(x2, y2)
请问有多少种满足要求的粉刷方式?输出答案的最后5位即可。
【输入格式】
第一行两个数n, m,表示木板的长度,和指定规则的条目个数。
接下来m行,每行四个数x1,y1,x2,y1,此规则表示A(x1, y1) 需要等于 A(x2, y2)

【输出格式】
一个整数,表示答案的末5位。

#include<cstdio>
#include<cstring>
#define fi first 
#define se second
#define ll long long
#define ms(x,y) memset(x,y,sizeof(x))
using namespace std;


int f[16][16][16][16];
bool vis[16][16][16][16];
int r1x[105],r2x[105],r1y[105],r2y[105];

int main(){ 
    freopen("draw.in","r",stdin);
    freopen("draw.out","w",stdout);
    int n,m,tc;
    int c[4];
    tc=1;
    for(int tt=1;tt<=tc;tt++)
    {
        ms(vis,0);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%d%d%d%d",&r1x[i],&r1y[i],&r2x[i],&r2y[i]);
            r1x[i]--;r2x[i]--;
        }

        for(int i=1;i<=m;i++){
            for(c[0]=0;c[0]<=n;c[0]++)
            for(c[1]=0;c[1]<=n;c[1]++)
            for(c[2]=0;c[2]<=n;c[2]++)
            for(c[3]=0;c[3]<=n;c[3]++){
                if(c[r1x[i]]>=r1y[i] ^ c[r2x[i]]>=r2y[i])
                    vis[c[0]][c[1]][c[2]][c[3]]=1;
            }
        }


        ms(f,0);
        f[0][0][0][0]=1;
        for(int color=0;color<=255;color++){
            for(int col=0;col<=3;col++)
            for(c[0]=0;c[0]<=n;c[0]++)
            for(c[1]=0;c[1]<=n;c[1]++)
            for(c[2]=0;c[2]<=n;c[2]++)
            for(c[3]=0;c[3]<=n;c[3]++)
                if(c[col]<n){
                    int tmp=f[c[0]][c[1]][c[2]][c[3]];
                    c[col]++;
                    f[c[0]][c[1]][c[2]][c[3]] = (f[c[0]][c[1]][c[2]][c[3]] + tmp) % 100000;
                    c[col]--;
                }

            for(c[0]=0;c[0]<=n;c[0]++)
            for(c[1]=0;c[1]<=n;c[1]++)
            for(c[2]=0;c[2]<=n;c[2]++)
            for(c[3]=0;c[3]<=n;c[3]++)
                if(vis[c[0]][c[1]][c[2]][c[3]])
                    f[c[0]][c[1]][c[2]][c[3]]=0;
        }
        printf("%05d\n",f[n][n][n][n]);
    }
    return 0;
}
30分的做法:

dp[i][c1][c2][c3]表示第i行,第一列填到c1,第二列填到c2,,第三列填到c3的方法总数。转移时如果暴力枚举转移仍然会TLE,需要使用完全背包的降低维度思想,列出转移方程:dp[i][c1][c2][c3]= dp[i-1][c1][c2][c3] + dp[i] [c1-1][c2][c3] + dp[i] [c1][c2-1][c3] + dp[i][c1][c2][c3-1]。这样才不会TLE。

100分的做法:

换一种dp思路,我们不按照常规思维枚举行列,而是从小到大枚举颜色。若当前枚举到的颜色为i,我们使用dp[l1][l2][l3][l4]表示每一行使用当前颜色涂到哪一个位置。
假设有一行现在是123344556,现在涂7,显然涂7只能涂序列的后缀,比如涂成123777777,或者123344577才能符合条件。所以考虑枚举当前颜色涂到哪个后缀来转移,转移时同样需要使用完全背包的降维思想优化。
对于限制条件,我们需要把不符合限制条件的去掉,什么样的方案符合限制条件呢?对于限制条件A(x1, y1) = A(x2, y2),和当前枚举到的颜色i,要么x1行和x2行,当前颜色都涂到了y1,y2位置,要么都没有涂到y1,y2位置。除此之外的都是不合法的方案,我们事先处理好一个vis数组,vis[l1][l2][l3][l4]表示四行分别涂到l1,l2,l3,l4,是否可行,在dp时如果遇到标记不可行的vis,这dp值设为0即可。

棋盘(knight.in/knight.out)

有一个N*M的棋盘,要在上面摆上knight,每个格子可以放至多一个knight。
knight的攻击范围如下图:
所有knight不能互相攻击,请问总共有多少可行的摆法?答案对1000000007取模。
【输入格式】
第一行个数t,表示测试的组数。
接下来t组,每组两个整数,n和m。
【输出格式】
一共t行,第i行表示第i组的答案。

#include<cstdio>
#include<cstring>
#include<iostream>
#define ms(x,y) memset(x,y,sizeof(x))
#define ll long long
using namespace std;

const ll mod=(1e9)+7;
int n,m;
int fi[8]={-2,-1,1,2,2,1,-1,-2};
int fj[8]={1,2,2,1,-1,-2,-2,-1};
int lim;
typedef ll arr[1<<8][1<<8];
arr A,B,s,tmparr;
int p[3][4];
arr storea[5];
arr stores[5];
bool isstore[4];

bool check(int state) {
    ms(p,0);
    int tmp=state;
    for(int i=1;i>=0;i--)
        for(int j=m-1;j>=0;j--,tmp>>=1)
            p[i][j]=tmp&1;
    for(int i=0;i<2;i++)
        for(int j=0;j<m;j++)
            if(p[i][j])
                for(int d=0;d<8;d++) {
                    int ti=i+fi[d],tj=j+fj[d];
                    if(ti>=0 && tj>=0 && ti<=2 && tj<m){
                        if(ti<2 && p[ti][tj]) return false;
                        if(ti==2) p[ti][tj]=1;
                    }
                }
    int line3=0;
    for(int j=0;j<m;j++)
        line3=(line3<<1)+p[2][j];
    int limm=(1<<m)-1;
    line3=(~line3)&limm;   
    for(int i=0;i<=limm;i++)
        if((i|line3)==line3)
            A[state][i+((state&limm)<<m)]=1;
    return true;
}
void Multi(arr A,arr B,arr C) {
    ms(tmparr,0);
    for(int i=0;i<=lim;i++)
        for(int j=0;j<=lim;j++)
            for(int k=0;k<=lim;k++)
                tmparr[i][j]=(tmparr[i][j]+A[i][k]*B[k][j])%mod;
    for(int i=0;i<=lim;i++)
        for(int j=0;j<=lim;j++)
            C[i][j]=tmparr[i][j];
}
int main()
{
    freopen("knight.in","r",stdin);
    freopen("knight.out","w",stdout);
    int tc;
    ms(isstore,0);
    scanf("%d\n",&tc);
    while(tc--){
        scanf("%d%d",&m,&n);
        if(n==1){
            printf("%d\n",1<<m);
            continue;
        }
        ms(A,0);ms(s,0);ms(B,0);
        n-=2;
        lim=(1<<2*m)-1;
        if(!isstore[m]) {
            for(int i=0;i<=lim;i++) {
                if(check(i))s[0][i]=1;
                else s[0][i]=0;
            }
            for(int i=0;i<=lim;i++)
                for(int j=0;j<=lim;j++)
                {
                    stores[m][i][j]=s[i][j];
                    storea[m][i][j]=A[i][j];
                }
            isstore[m]=true;
        }
        else{
            for(int i=0;i<=lim;i++)
                for(int j=0;j<=lim;j++)
                {
                    s[i][j]=stores[m][i][j];
                    A[i][j]=storea[m][i][j];
                }
        }
        for(int i=0;i<=lim;i++)
            B[i][i]=1;

        while(n) {
            if(n&1)Multi(B,A,B);
            Multi(A,A,A);
            n>>=1;
        }
        Multi(s,B,s);
        ll ans=0;
        for(int i=0;i<=lim;i++)
            ans=(ans+s[0][i])%mod;
        cout<<ans<<endl;
    }
    return 0;
}
状态压缩动态规划

每一行使用一个二级制串表示当前行的摆knight情况。用f[i][j][k]表示第i行,前一行为j,前一行的前一行为k时的方法总数,转移时枚举三行的状态,判断合法情况转移即可。
如果枚举每一行转移,可以得70分。如果将状态转移矩阵化,使用矩阵快速幂,可以通过全部测试数据。

以上:2017.3.25

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值