动态规划测试test20170506

17 篇文章 0 订阅

前言

这次考试整体都不是很好,我也一样的挂的非常非常的惨,这中类dp题竟然这么难吗?

试题

1.巨魔有金币(gold.pas/c/cpp)

Time Limit:0.1s Memory Limit:256MB

【题目描述】

某巨魔去了一趟拍卖行卖东西,赚了不少金币。
该巨魔由于报复社会不成被社会报复后决定报复做题的众人。已知现在在 WOW 中有 4种面值的金币(我骗你们的,我说了我要报复你们!哦呵呵!),面值大小分别为 c1、c2、c3、c4。巨魔问你 T 次,每次询问:当他有 si 枚面值为 ci 的金币时,他有多少种付钱方式买下价值为 V 的物品,两种付钱方式不同当且仅当两种付钱方式中存在至少一种面值的金币使用的数量不同。

【输入格式】

第一行 5 个数字 c1、c2、c3、c4、T。
接下来 T 行,每行 5 个数字,分别为 s1、s2、s3、s4、V。

【输出格式】

输出 T 行,每行一个数字,各对应一个询问的答案。

【样例输入输出】

Gold.in
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

Gold.out
4
27

【数据范围】

100%的数据 1T1000 1siV100000

【题解】

做过原题的我考试结果打挂了,结果是for循环搞错了方向…

原题:bzoj 1042 [HAOI2008]硬币购物

这道题目就是不可以重复使用的01背包问题。用dp预处理完之后再用容斥原理减值或者加值。
dp[i] 为“不限制硬币”下的最多方法数,那么状态转移方程就是 dp[j]=dp[j]+dp[jc[i]]
解决完这些后,我们来看容斥原理
设第1枚硬币超过最大值,也就是大于携带的数量。那么就要将总方案数减去第一枚硬币超过最大值的方案,以此类推。那么按照容斥定理,单数的我们就要减去,双数我们就要加上。这样就能得出正确答案。
大概就这样就可以了。

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;

const int size = 110000;
LL f[size];
LL c[size],d[size];

int main() {
    freopen("gold.in","r",stdin);
    freopen("gold.out","w",stdout);
    LL T,tot;
    for(int i=1;i<=4;i++)scanf("%lld",&c[i]);
    scanf("%lld",&T);
    memset(f,0,sizeof(f));f[0]=1;
    for(int i=1;i<=4;i++)
        for(int j=c[i];j<=100000;j++)
            f[j]+=f[j-c[i]];
    while(T--) {
        for(int i=1;i<=4;i++) {scanf("%d",&d[i]);d[i]++;}
        scanf("%lld",&tot);
        LL ans=f[tot];
        for(int i=1;i<=4;i++)if(tot>=d[i]*c[i])ans-=f[tot-d[i]*c[i]];
        for(int i=1;i<=3;i++)for(int j=i+1;j<=4;j++)if(tot>=d[i]*c[i]+d[j]*c[j])ans+=f[tot-d[i]*c[i]-d[j]*c[j]];
        for(int i=1;i<=2;i++)for(int j=i+1;j<=3;j++)for(int k=j+1;k<=4;k++)if(tot>=d[i]*c[i]+d[j]*c[j]+d[k]*c[k])ans-=f[tot-d[i]*c[i]-d[j]*c[j]-d[k]*c[k]];
        if(tot>=d[1]*c[1]+d[2]*c[2]+d[3]*c[3]+d[4]*c[4])ans+=f[tot-d[1]*c[1]-d[2]*c[2]-d[3]*c[3]-d[4]*c[4]];
        printf("%lld\n",ans);
    }
    return 0;
}

2.巨魔没金币(silver.pas/c/cpp)

Time Limit:1s Memory Limit:256MB

【题目描述】

某巨魔去了一趟拍卖行卖东西,赚了不少金币,然后又买了些东西,就没金币了。
该巨魔没了金币,但他还有很多银币。他搞了张圆桌。划分出 2N 个位置标号 1 到 2N ,然后把 N 个银币放在奇数位置上。接下来每次按如下操作:在任意两个银币之间放上一个银币,然后将原来的银币拿走;所放银币的正反面由它两边的两个银币决定,若两个银币均为正面朝上或反面朝上,则所放银币为正面朝上,否则为反面朝上。 那么操作 T 次之后桌子边缘上银币的情况会是怎样的呢?

【输入格式】

第一行包含两个整数 n 和 T。
接下的一行包含 n 个整 数,表示最开始桌面边缘的硬币摆放情况,第 i 个整数 ai表示第 i 个硬币摆放在 2i1 个位置上, ai=1 表示正面朝上, ai=2 表示反面朝上。

【输出格式】

输出一行 2n 个整数,其中第 i 个整数 bi桌面边缘的第 i 个位置上硬币的情况, bi=1 表示正面朝上, bi=2 表示反面朝上, bi=0 表示没有硬币。

【样例输入输出】

silver.in
10 5
2 2 2 1 1 1 1 1 1 2

silver.out
0 1 0 1 0 1 0 1 0 2 0 1 0 2 0 1 0 1 0 1

【数据范围】

30%的数据: 1N1000 1T1000
100%的数据: 1N100000 1T260

【题解】

原题:bzoj1411: [ZJOI2009]硬币游戏
我们可以把每两次操作看成是一次操作,那么每经过一次操作后所有的硬币的位置都不会变化。很容易可以得知每一次操作时的第i个硬币是由上一次操作后的第 i+1 个硬币和第 i1 个硬币异或而来。那么现在我们来求证在第 2k 次操作时硬币i由初始状态的第 i+2k 和第 i2k 个硬币异或而来。首先当 k=0 时是满足条件的,那么我们假设当 k=n 时满足条件,则当 k=n+1 时每一个硬币 i 由经过2n次操作后的状态的第 i+2n 和第 i2n 个硬币异或而来,设这两个硬币为 x1 x2 ,那么 x1 就是由初始状态的第 i 和第i+2n+1个硬币异或而来, x2 是由初始状态的第 i 和第i2(n+1)个硬币异或而来,那么很容易发现第i个硬币被抵消掉了,那么当 k=n+1 时第i个硬币就由初始状态下的第 i+2n+1 i2n+1 个硬币得到。得证!
这样,每次计算 2k 后的状态,我们就可以在 O(nlog2T) 的时间内解决此题。

【代码】

#include <cstdio>
#include <cstring>
#include <algorithm>

typedef long long LL;
const int size = 100000+10;
int a[size],b[size],n;
LL m,s,m1;bool flag;

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

void init() {
    read(n);read(m);
    for(int i=1;i<=n;i++)
        read(a[i]),--a[i];
}

void work() {
    m1=m;
    if(m%2==1) {
        --m;
        for(int i=1;i<=n;i++) 
            b[i]=a[i]^a[i%(n+1)];
        memcpy(a,b,sizeof b);
    }
    m=m/2;
    while(m>0) {
        s=(m&-m);
        for(int j=1;j<=n;j++)
            b[j]=a[((j-s)%(n+n-1))%(n+1)]^a[(j+s-1)%(n+1)];
        memcpy(a,b,sizeof b);
        m=m-s;
    }
    flag=true;
    for(int i=1;i<=n;i++) {
        if(m1%2==1) {
            if(flag) {
                flag=false;printf("0");
            }
            else printf(" 0");
        }
        if(flag) {
            printf("%d",a[i]+1);
            flag=false;
        }
        else printf(" %d",a[i]+1);
        if(m1%2==0) printf(" 0");
    }
}

int main() {
    init();work();
    return 0;
}

3.守卫部署(Guard.pas/c/cpp)

Time Limit:1s Memory Limit:256MB

【问题描述】

征夷王(征夷大白兔王子青王神人)最近打算灭掉越南(南夷)。征夷王的国家由 N 个城市、N-1 条双向道路组成,其中,任意两个城市之间有且仅有一条通路连接。然而,万恶的日本(东倭)想在此时偷袭征夷王的国家!每当一个城市被袭击时,与之有直接道路相连的城市或该城市中若有一只守卫军队,便可以化险为夷。但是,如果与之有直接道路相连的城市和该城市中没有一个城市有一只守卫军队,那么这个城市就悲剧了。征夷王显然不愿意看到自己的国家侵略。所以他在某些城市部署若干只守卫军队,使得任意一个城市被袭击时均可化险为夷。但是,为了尽可能迅捷地灭掉南夷,征夷王希望部署的守卫军队只数最少。现在伟大的王想知道,他最少要部署多少只守卫军队?

【输入】

输入文件名为 Guard.in。
输入第一行一个正整数 N,代表征夷王国家拥有的城市个数。下接 N-1 行,每行两个正整数 Xi 和 Yi,表示编号为 XI 和 YI 的城市之间有一条双向道路。

【输出】

输出文件名为 Guard.out。
输出第一行一个正整数,代表最少要部署的军队个数。

【输入输出样例】

Guard.in
5
1 3
5 2
4 3
3 5
Guard.out
2

【数据范围】

30%的数据, 1N10
100%的数据, 1N100000

然而并不会…

总结

哎~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值