CF283C Coin Troubles

2020.11.2

题目描述

\(n\) 种不同的硬币,每个硬币有一个价值 \(a_i\)​(不同硬币价值可能相同).

\(Bessie\) 有若干种选择硬币的方法使得选出的硬币总价值为 \(t\)\(Bessie\) 会给你 \(q\)\((b,c)\),并告诉你第 \(b\) 种硬币的数量严格大于第 \(c\) 种硬币的数量。保证这 \(q\)\((b,c)\) 中所有 \(b\) 都不同且所有 \(c\) 也都不同。
问有多少种选择硬币的方法满足选出的硬币总价值为 \(t\)

\(Bessie\) 认为两种方案不同指的是两种方案存在至少一种硬币数量不同。

答案对 \(10^9+7\) 取模。

解法

对于一个约束条件 \((b_i,c_i)\) ,建一条 \(c_i\to b_i\) 的边(反图)。

可以看出一个点的入度和出度小于等于一,那么这个图一定是由若干个简单环和链组成的。有环显然答案为零,只考虑链的情况。

先构造一组最小的合法的方案,所组成的价值即为每个节点的价值乘上其到根节点的距离,任意其它的方案多会比此方案多,我们不妨先用 \(t\) 减去这个值。对于一种硬币 \(i\),我们如果想要再用掉一个且满足约束条件的话,那么 \(i\) 的整个子树里面的硬币都需要再用一次,即我们可以把每个物品的价值直接改为其子树价值和。可以发现这样总是满足条件的,那么现在的问题就转换为完全背包了。

#include<stdio.h>
#include<queue>
using namespace std;
#define M 1000007
#define N 507
#define ll long long
#define Mod 1000000007

inline ll read(){
    ll x=0; bool flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

struct E{
    int next,to;
}e[N];
int head[N],cnt=0,n,m;
bool in[N];
queue<int> Q;
ll dp[M],t,a[N],dep[N];

inline void add(int id,int to){
    e[++cnt]=(E){head[id],to};
    head[id]=cnt;
}

void dfs(int u){
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        dfs(v);
        a[u]+=a[v];
    }
}

int main(){
//    freopen("coin.in","r",stdin);
//    freopen("coin.out","w",stdout);
    n=read(),m=read(),t=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read();
        add(v,u),in[u]=1;
    }
    for(int i=1;i<=n;i++)
        if(!in[i]) dep[i]=1,Q.push(i);
    while(!Q.empty()){
        int u=Q.front();
        Q.pop();
        for(int i=head[u];i;i=e[i].next){
            int v=e[i].to;
            dep[v]=dep[u]+1;
            t-=(dep[u]*a[v]);
            Q.push(v);
        }
    }
    for(int i=1;i<=n;i++)
        if(!dep[i]){
            printf("0");
            return 0;
        }
    for(int i=1;i<=n;i++)
        if(dep[i]==1) dfs(i);
    dp[0]=1;
    if(t<0){
        printf("0");
        return 0;
    }
    for(int i=1;i<=n;i++)
        for(ll j=a[i];j<=t;j++)
            dp[j]=(dp[j]+dp[j-a[i]])%Mod;
    printf("%lld",dp[t]);
}
posted @ 2020-11-03 10:31  Kreap  阅读(84)  评论(2)    收藏  举报