2017.3.25 机房测试(数位DP,状压DP,矩阵)

本文解析了四个动态规划问题:天平平衡问题,涉及寻找使天平平衡的秤砣配置方案;山峰数问题,探讨如何判断及计数特定类型的数字;粉刷匠2问题,讨论在特定规则下对木板进行粉刷的方法;以及棋盘问题,研究骑士放置的数量。

cky远在上海仍不忘为我们出题。。。
这次是DP专题。。。

天平(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]内。
【输出格式】
一个整数,代表能使得天平平衡的方案数。
【输入样例】
2 4
-2 3
3 4 5 8
【输出样例】
2
【样例解释】
方案1: (-2)*(3+4+5) + 3*8 = 0
方案2: (-2)(4+8) + 3(3+5) = 0
【数据规模】
10% 数据满足2≤n,m≤4。
100% 数据满足2≤n,m≤20。
【解题报告】

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<time.h>
using namespace std;

long long n,m,ans=0;
long long f[25][15001]={0},c[25],r[25];

int main()
{
    freopen("balance.in","r",stdin);
    freopen("balance.out","w",stdout);
    scanf("%I64d%I64d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%I64d",&c[i]);
    for(int i=1;i<=m;i++)
    scanf("%I64d",&r[i]);
    f[0][7500]=1;
    for(int i=1;i<=m;i++)
    for(int k=1;k<=n;k++)
    for(int j=0;j<=15000;j++)
    {
        if(j-c[k]*r[i]<0||j-c[k]*r[i]>15000) continue;
        f[i][j]+=f[i-1][j-c[k]*r[i]];
    }
    printf("%I64d",f[m][7500]);
    return 0;

}

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

山峰数是指数字排列中不存在山谷(先降后升)的数,例如0,5,13,12321都是山峰数,101,1110000111都不是山峰数。
现给出n个数,请依次判断它们是否为山峰数,如果不是,输出-1。如果是,求出比它小的数中有多少个山峰数。
【输入格式】
第一行一个数n,表示询问数目。
接下来n行,每一行¬¬¬一个数x,表示询问的数。
【输出格式】
输出有n行,x如果不是山峰数,输出-1。x如果是山峰数,则输出有多少个比它小的山峰数。
【输入样例】
5
10
55
101
1000
1234321
【输出样例】
10
55
-1
715
94708
【数据规模】
20% 数据满足x ≤ 106。
100% 数据满足n ≤ 10, x ≤ 1060
【解题报告】

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

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

long long 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];
    long long 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) 
        {
            memset(f,-1,sizeof(f));
            printf("%I64d",dfs(1,0,0,1)-1);
        }
        if(tc!=0)
        printf("\n");
    }
    return 0;
}

粉刷匠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位。
【输入样例1】
1 0
1 3
【输出样例1】
67296
【输入样例2】
1 3
1 1 2 1
1 1 3 1
4 1 3 1
【输出样例2】
00256
【提示】
输出可以使用%05d输出。
【数据规模】
30% 数据满足 n ≤ 3,m = 0
100% 数据满足1 ≤ n ≤ 15,0 ≤ M ≤ 100,X1,X2≤4,Y1,Y2≤n
【解题报告】

代码如下:

#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;
}

棋盘(knight.in/knight.out)

有一个N*M的棋盘,要在上面摆上knight,每个格子可以放至多一个knight。
knight的攻击范围如下图:
这里写图片描述
所有knight不能互相攻击,请问总共有多少可行的摆法?答案对1000000007取模。
【输入格式】
第一行个数t,表示测试的组数。
接下来t组,每组两个整数,n和m。
【输出格式】
一共t行,第i行表示第i组的答案。
【输入样例】
4
1 2
2 2
3 2
3 31415926
【输出样例】
4
16
36
933912086
【数据规模】
70% 数据满足m≤100。
100% 数据满足t≤10, n≤3, m≤109。

代码如下:

#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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值