【DP】TEST20170506

10 篇文章 0 订阅

守卫部署(Guard.pas/c/cpp Time Limit:1s Memory Limit:256MB)

问题描述

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

输入

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

输出

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

输入样例

5 
1 3
5 2 
4 3 
3 5

输出样例

2

分析

    算法:树形DP
    用f[i][0]表示在i节点设军队,保证i及其所有子孙节点不受侵略的最小费用,f[i][1]表示在i节点的子节点设军队,保证i及其所有子孙节点不受侵略的最小费用,f[i][2]表示在i节点的父节点设军队,保证i及其所有子孙节点不受侵略的最小费用。
    则有递推式如下:

f[i][0]=1+kson[i]min(f[k][0],f[k][1],f[k][2]);

f[i][1]=min{f[k][0]kson[i]+uson[i],u!=kmin(f[u][0],f[u][1])};

f[i][2]=kson[i]min(f[u][0],f[u][1]);

    另要注意,叶子节点的初始化如下:
    f[i][0]=f[i][1]=1;f[i][2]=0;

代码

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

const int sm = 1e5+5;
const int inf = 0x3f3f3f3f;

int n,s,x,y,z;
int head[sm];
struct edge{
    int to,nxt;
}e[sm*2];
int sum=0,gsum=0;
int f[sm][3],g[sm][2];

void add(int from,int to) {
    e[++s].to=to;
    e[s].nxt=head[from];
    head[from]=s;
}

void dfs(int x,int y){
    bool flag=0;
    for(int i=head[x];i;i=e[i].nxt)
        if(e[i].to!=y)dfs(e[i].to,x),flag=1;
    if(!flag) {
        f[x][0]=f[x][1]=1;
        f[x][2]=0;  
    }
    else {
        sum=0;g[sm][2];gsum=0;
        for(int i=head[x];i;i=e[i].nxt) {
            if(e[i].to==y)continue;
            int t=e[i].to;
            sum+=min(f[t][0],min(f[t][1],f[t][2]));
            g[t][0]=f[t][0];g[t][1]=min(f[t][0],f[t][1]);
            gsum+=g[t][1];
        }
        f[x][0]=++sum;
        sum=inf;
        for(int i=head[x];i;i=e[i].nxt) {
            if(e[i].to==y)continue;
            sum=min(sum,g[e[i].to][0]+gsum-g[e[i].to][1]);
        }
        f[x][1]=sum;
        f[x][2]=gsum;
    }
}

int main() {

    freopen("Guard.in","r",stdin);
    freopen("Guard.out","w",stdout);

    memset(f,0x3f,sizeof(f));
    scanf("%d",&n);
    for(int i=1;i<n;++i) {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }

    dfs(1,-1);

    printf("%d\n",min(f[1][0],f[1][1]));
    return 0;
}

巨魔没金币(silver.pas/c/cpp Time Limit:1s Memory Limit:256MB)

问题描述

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

输入

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

输出

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

输入输出样例

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

分析

    不知道怎么找出的规律,对于第k次操作有

a[i]=a[i+2k]Xor a[i2k]

    当然,由于k非常大, i+2k i2k 是要取模的。

代码

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

typedef long long LL;

const int sm = 1e5+5;

LL n,m,T;
LL a[sm],b[sm];
char ch;

void read(LL &x) {
    x=0;
    ch=getchar();
    while(ch>'9'||ch<'0')
        ch=getchar();
    while(ch>='0'&&ch<='9')
        x=x*10+ch-'0',ch=getchar();
}

int main() {

    read(n);read(T);
    for(int i=0;i<n;++i)
        read(a[i]),a[i]--;

    a[n]=a[0];
    m=T;
    if(m&1)for(int i=0;i<n;++i)a[i]^=a[i+1];
    m>>=1;
    while(m) {
        LL t=m&-m;
        for(int i=0;i<n;++i) {
            LL k=((i-t)%n+n)%n,l=(i+t)%n;
            b[i]=a[k]^a[l];
        }
        memcpy(a,b,sizeof(a));
        m-=t;
    }
    if(T&1)printf("0 %lld",a[0]+1);
    else printf("%lld 0",a[0]+1);
    for(int i=1;i<n;++i) {
        if(T&1)printf(" 0 %lld",a[i]+1);
        else printf(" %lld 0",a[i]+1);
    }
    return 0;
}

来源

ZJOI2009 硬币游戏

巨魔有金币(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。〈=10000)

输出

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

输入输出样例

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

gold.out
4
27  

数据范围

    100%的数据 1≤T≤1000、1≤si,V≤100000。

分析

    算法:容斥原理+DP
    根据容斥原理:

ans=12+34

    记f[i]为物品价值为i,用任意数量的c[1],c[2],c[3],c[4]硬币恰好将物品买下的方案数。那么,第i枚硬币超限的方案数:
f[V(1+d[i]c[i])]

    多枚硬币超限的方案数:
f[V(1+d[i])c[i] ]

     代码用dfs实现。

代码

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

const int sm = 1e5;

typedef long long LL;

int T,V;
int c[5],s[5];
LL ans,f[sm+5];

void dfs(int x,int k,int sum){
    if(sum<0)return;
    if(x==5) {
        ans+=(k&1?-1:1)*f[sum];
        return;
    }
    dfs(x+1,k+1,sum-(s[x]+1)*c[x]);
    dfs(x+1,k,sum);
}

int main() {

    freopen("gold.in","r",stdin);
    freopen("gold.out","w",stdout);

    for(int i=1;i<=4;++i)
        scanf("%d",&c[i]);
    scanf("%d",&T);

    f[0]=1;
    for(int i=1;i<=4;++i)
        for(int j=c[i];j<=sm;++j)
            f[j]+=f[j-c[i]];

    for(int i=1;i<=T;++i) {
        for(int j=1;j<=4;++j)
            scanf("%d",&s[j]);
        scanf("%d",&V);
        ans=0;
        dfs(1,0,V);
        printf("%lld\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值