CF283C Coin Troubles
2020.11.2
Link
题目描述
有 \(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]);
}

浙公网安备 33010602011771号