动态规划测试test20170520

来源:互联网 发布:手机淘宝解绑手机号码 编辑:程序博客网 时间:2024/05/17 02:52

前言

这次考试幸好看过棋盘型dp的相关文章(然后发现第三题做过),于是第一题在打表找规律下加乱搞得到递推式,然后就发现还要高精,然后打压位高精过了。第二题明知道是转化为单调队列下的多重背包处理,可是不会打,然后就爆0了。

试题

罗大师的棋盘(chessboard.pas/c/cpp)

Time:0.05s Memory:256M

【问题描述】

罗大师有一个 8*8 的棋盘。有一天罗大师有点无聊,于是想将这个切割这个棋盘。每次
将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割
了(n-1)次后,连同最后剩下的矩形棋盘共有 n 块矩形棋盘。(每次切割都只能沿着棋盘格子
的边进行)。原棋盘的每个格子上都有一个权值,剩下的矩形棋盘的总分为其所含各个格子
的权值之和。罗大师希望分得的 N 个矩形棋盘的权值相差比较小,也就是均方差最小。罗
大师想知道,最小的可能的均方差为多少?

【输入】

输入文件名为 chessboard.in。
输入一个数 N,代表罗大师要切割成的块数。
后接一个 8*8 的矩阵,表示每个格子上的

【输出】

输出文件名为 chessboard.out。
输出一个实数,为最小的可能的均方差。(四舍五入到小数点后 3 位)

【输入输出样例】

chessboard.in
3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3
chessboard.out
1.633

【数据范围】

测试点编号 N 的值
1       3
2       4
3       5
4       6
5       7
6       8
7       9
8       10
9       11
10       12
并且保证每个格子上的权值为一个不大于 100 的非负整数。

【题解】

我们可以很容易知道方差的公式其实是可以化解的为S2=1n(ini=1a2inx¯2)

要使方差最小,只需使xi2最小即可,即各块分值平方和最小。x¯是个固定的数,跟如何分割无联系.

首先定义状态:dp[k][x1][y1][x2][y2],表示左上角坐标(x1,y1)到右下角坐标(x2,y2)区域的棋盘经过k次分割后,各块分值平方和的最小值。则状态转移方程为:

dp[k][x1][y1][x2][y2]=mindp[0][x1][y1][t][y2]+dp[k1][t+1][y1][x2][y2],(x1tx2)dp[k1][x1][y1][t][y2]+dp[0][t+1][y1][x2][y2],(x1tx2)dp[0][x1][y1][x2][t]+dp[k1][x1][t+1][x2][y2],(y1ty2)dp[k1][x1][y1][x2][t]+dp[0][x1][t+1][x2][y2](y1ty2)

dp[0][x1][y1][x2][y2]表示左上角坐标(x1,y1)到右下角坐标(x2,y2)区域的棋盘的分值和平方

【代码】

#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#define Max(a,b) ((a)>(b)?(a):(b))#define Min(a,b) ((a)<(b)?(a):(b))const int size = 10;const int INF = 1<<30;int num[size][size];int sum[size][size];double dp[14][size][size][size][size];int n,tot;double ans;//返回左上角坐标(x1,y1)到右下角坐标(x2,y2)区域的棋盘的分值和平方inline double calc(int x1,int y1,int x2,int y2) {    double ret=(double)(sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]);    return ret*ret;}int main() {    freopen("chessboard.in","r",stdin);    freopen("chessboard.out","w",stdout);    scanf("%d",&n);    for(int i=1;i<=8;i++) {        for(int j=1;j<=8;j++) {            scanf("%d",&num[i][j]);            //sum[i][j]表示棋盘(1,1)到(i,j)区域的累计分值            sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+num[i][j];            //tot表示整个棋盘的分值之和            tot+=num[i][j];        }    }    //初始化dp    for(int x1=1;x1<=8;x1++)        for(int y1=1;y1<=8;y1++)            for(int x2=x1;x2<=8;x2++)                for(int y2=y1;y2<=8;y2++)                    dp[0][x1][y1][x2][y2]=calc(x1,y1,x2,y2);    for(int k=1;k<n;k++)        for(int x1=1;x1<=8;x1++)            for(int y1=1;y1<=8;y1++)                for(int x2=x1;x2<=8;x2++)                    for(int y2=y1;y2<=8;y2++) {                        dp[k][x1][y1][x2][y2]=(double)INF;                        for(int t=x1;t<x2;t++) dp[k][x1][y1][x2][y2] = Min(Min(dp[k][x1][y1][x2][y2],dp[k-1][x1][y1][t][y2]+dp[0][t+1][y1][x2][y2]),dp[0][x1][y1][t][y2]+dp[k-1][t+1][y1][x2][y2]);                        for(int t=y1;t<y2;t++) dp[k][x1][y1][x2][y2] = Min(Min(dp[k][x1][y1][x2][y2],dp[k-1][x1][y1][x2][t]+dp[0][x1][t+1][x2][y2]),dp[0][x1][y1][x2][t]+dp[k-1][x1][t+1][x2][y2]);                    }    //计算方差平方    double ans = dp[n-1][1][1][8][8]*1.0/n-((double)tot*1.0/n)*((double)tot*1.0/n);    printf("%.3lf\n",sqrt(ans));    return 0;}

举火燎天(lights.pas/c/cpp)

Time:1s Memory:256M

【问题描述】

征夷王将越南(南夷)攻下来之后,决定用 N 个火炬摆成一个圆圈,围住越南的都城,
以举火燎天宣告自己的胜利。这 N 个火炬的位置已经固定了,但是,每个火炬的颜色都没
定,总共有 M 种颜色的火炬。为了显示我们中华文化的博大精深,征夷王决定,相邻的火
炬的颜色不能相同。现在征夷王想知道,满足他要求的方案有多少个。由于火炬的位置已经
固定,所以即便旋转翻转之后两种方案相同也视作不同方案。

【输入】

输入文件名为 lights.in。
输入一行两个正整数 N 和 M,意义如上所述。

【输出】

输出文件名为 lights.out。
输出一行一个正整数,代表方案总数。

【输入输出样例】

lights.in
3 4
lights.out
24

【数据范围】

对于 100%的数据,1N,M100

【题解】

f[i][1] 表示第 i 个火炬与第 1 个火炬颜色相同的方案个数,而 f[i][0] 表示第 i 个火炬与第 1 个火炬颜色不同的方案个数。

f[1][0]=0f[1][1]=m

f[i][0]=f[i1][1](m1)+f[i1][0](m2)

f[i][1]=f[i1][0]

【代码】

#include <cstdio>#include <cstring>#include <algorithm>#define Max(a,b) ((a)>(b)?(a):(b))#define Min(a,b) ((a)<(b)?(a):(b))typedef long long LL;const LL Mod = 100000000;struct Bignum {    LL a[110];    int len ;    Bignum (LL x = 0) {        this->clear();a[0]=x;    }    void print() {        printf("%lld",a[len-1]);        for(int i=len-2;i>=0;i--)             printf("%08lld",a[i]);        puts("");    }    void clear() {        memset(a,0,sizeof a);        len = 1;    }    Bignum operator + (const Bignum &h) {        Bignum ans;        ans.len = Max(len,h.len)+1;        for(int i=0;i<ans.len;i++) {            ans.a[i] = ans.a[i]+h.a[i]+a[i];            ans.a[i+1] += ans.a[i]/Mod;            ans.a[i] %= Mod;        }        if(!ans.a[ans.len-1])            --ans.len;        return ans;    }    Bignum operator * (const Bignum &h) {        Bignum ans;        ans.len = len+h.len;        for(int i=0;i<len;i++)             for(int j=0;j<h.len;j++)                 ans.a[i+j] = a[i]*h.a[j];        for(int i=0;i<ans.len;i++) {            ans.a[i+1] +=ans.a[i]/Mod;            ans.a[i] %= Mod;        }        if(!ans.a[ans.len-1])            --ans.len;        return ans;    }}f[120][2],s1,s2;LL n,m;int main() {    freopen("lights.in","r",stdin);    freopen("lights.out","w",stdout);    scanf("%lld%lld",&n,&m);    if(n==1) {        printf("%lld\n",m);        return 0;    }    f[1][1].a[0]=m;    s1.a[0]=m-1;s2.a[0]=m-2;    for(int i=2;i<=n;i++) {        f[i][1]=f[i-1][0];        f[i][0]=f[i-1][0]*s2+f[i-1][1]*s1;    }    f[n][0].print();    return 0;}

罗大师的采药(medic.pas/c/cpp )

Time:0.2s Memory:256M)

【问题描述】

罗大师掉到了一个山洞,里面有 N 株药材。但是罗大师要教济远小朋友题目,所以他
必须赶快离开。但是罗大师需要采药来赚点外快。采某株药需要 Ti 个单位时间,同时这株
草药价值 Vi 个金币。罗大师为了考验高二小朋友,所以就提出了 Q 个问题,每个询问是“如
果我只有 Mi 个单位时间,我最多能得到多少个金币?”现在,为了高二机房的尊严,请你
们回答罗大师的询问。

【输入】

输入文件名为 medic.in。
输入文件第一行为 N 和 Q,代表有 N 株草药和 Q 个询问。
后接 N 行,每行两个整数,分别为采第 I 株草药的时间 Ti 和其价格 Vi。
后街 Q 行,每行一个正整数 Mi,代表对于每个询问。

【输出】

输出文件名为 medic.out。
输出 Q 行,每行一个整数,代表对于罗大师的每个询问你得出的答案。

【输入输出样例】

medic.in
3 1
10 10
8 1
1 2
9
medic.out
3

【数据范围】

对于 50%的数据,1N,Mi,Q1000,;
对于 100%的数据,1N,Mi,Q100000Ti,Vi10

【题解】

转化为单调队列下的多重背包处理

【代码】

#include <cstdio>#include <algorithm>#include <cstring>template< typename Type >inline void Read( Type &In ) {    In=0;    char ch=getchar();    for( ; ch> '9'||ch< '0'; ch=getchar() );    for( ; ch>='0'&&ch<='9'; ch=getchar() )In = In*10 + ch-'0';}const int maxn = 1e5+10;int n , q , t , v , x , c , M , tmp;int Que[maxn][2] , H , T;int m[maxn] , Sz[11][11];int f[maxn];int main() {    freopen("medic.in","r",stdin);    freopen("medic.out","w",stdout);    Read( n );    Read( q );    For( i , 1 , n )Read( t ) , Read( v ) , Sz[t][v]++;    For( i , 1 , q )Read( m[i] ) , M = max( M , m[i] );    for(int i=1; i<=10; i++)        for(int j=1; j<=10; j++)            if( Sz[i][j] ) {                t = i;                v = j;                x = M / t;                c = std::min( x , Sz[i][j] );                for(int u=0; u<=t-1; u++) {                    H = T = 0;                    Que[0][1] = f[u] + x*v;                    for(int k=1; k<=x; k++) {                        int tmp = j + k*t;                        if( tmp > M )break;                        while( H <= T && k - Que[H][0] > c )H++;                        while( H <= T && Que[T][1] <= f[ tmp ] + ( x-k )*v )T--;                        T++;                        Que[T][0] = k;                        Que[T][1] = f[ tmp ] + ( x-k )*v;                        f[ tmp ] = std::max( f[ tmp ] , Que[H][1] - ( x-k )*v );                    }                }            }    for(int i=1; i<=q; i++)        printf("%d\n",f[m[i]]);    return 0;}

总结

其实这次考试还算不错吧,但其实讲过的题都不会打,还是需要落实啊。

原创粉丝点击