C - Half and Half
题意:你需要买两种披萨
A
A
分别
Ai
A
i
Bi
B
i
个。
Ai
A
i
Bi
B
i
为整数
有
3
3
种购买方式。
买 个
A
A
买 个
B
B
.
买 个
A
A
和 个
B
B
三种购买方式价格不相同,求最小花费。
简单贪心即可。
#include<bits/stdc++.h>
using namespace std;
int A,B,C,X,Y;
int main(){
scanf("%d%d%d%d%d",&A,&B,&C,&X,&Y);
if(C*2>A+B){
printf("%d\n",A*X+B*Y);
}
else {
int D=C*2,num=min(X,Y),ans=0;
ans+=D*num;
X-=num;Y-=num;
if(X){
if(D<A)ans+=D*X;
else ans+=X*A;
}
else {
if(D<B)ans+=D*Y;
else ans+=B*Y;
}
printf("%d\n",ans);
}
return 0;
}
D - Static Sushi
寿司店是环形吧台,周长 米。
你在初始点
,题目按照距离该点顺时针方向给出那个地方的相对距离 与所放寿司的能量值
你可以随便走 但每走
1
1
米要花费 的能量值 可以随时结束 最大化结束时的能量值 初始能量值为
0
0
考虑你可以怎么走
顺时针走到结束
2
2
逆时针走到结束
以上前缀和 扫一下就好了
3
3
顺时针走到某点后 逆时针走到某点
逆时针走到某点后 顺时针走到某点
然后我当时就看了眼数据范围可以
nlogn
n
l
o
g
n
做。。
于是我就直接枚举了先走到某点,然后走回起始点的距离。然后用
ST
S
T
表查询接下来最大能获取的能量值。。
emmm
e
m
m
m
其实这题直接前缀和就好了
#include <bits/stdc++.h>
using namespace std;
const int MAXN=110000;
typedef long long ll;
ll c,n,ans=0,sum=0;
struct data{
ll dis,val;
}d[MAXN];
ll f[MAXN][21],g[MAXN][21],lg[MAXN];
void get_log(int n){
int log=0;
for(int i=1;i<=n;i++){
lg[i]=(1<<log+1==i)?++log:log;
}
}
ll querymaxf(int l,int r){
if(l>r)return 0;
int k=lg[r-l+1];
return max(f[l][k],f[r-(1<<k)+1][k]);
}
ll querymaxg(int l,int r){
if(l>r)return 0;
int k=lg[r-l+1];
return max(g[l][k],g[r-(1<<k)+1][k]);
}
void get1(){
ll sum=0;
for(int i=1;i<=n;i++){
sum+=d[i].val;
f[i][0]=sum-d[i].dis;
}
for(int i=1;(1<<i)<=n;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
}
}
}
void get2(){
ll sum=0;
for(int i=n;i>=1;i--){
sum+=d[i].val;
g[i][0]=sum-(c-d[i].dis);
}
for(int i=1;(1<<i)<=n;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
g[j][i]=max(g[j][i-1],g[j+(1<<(i-1))][i-1]);
}
}
}
ll baoli(int limit){
ll sum=0,M=0;
for(int i=1;i<=limit;i++){
sum+=d[i].val;
M=max(M,sum-d[i].dis);
}
return M;
}
int main(){
scanf("%lld%lld",&n,&c);
get_log(n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&d[i].dis,&d[i].val);
}
for(int i=1;i<=n;i++){
sum+=d[i].val;
ans=max(ans,sum-d[i].dis);
}
sum=0;
for(int i=n;i>=1;i--){
sum+=d[i].val;
ans=max(ans,sum-(c-d[i].dis));
}
get1();
get2();
//两个拼起来
ll prepos=0,premax=0,tmpsum=0;
//先顺时针走
for(int i=1;i<=n;i++){
tmpsum+=d[i].val;
if(tmpsum-(2*d[i].dis)>0){
premax=tmpsum-(2*d[i].dis);
// cout<<premax<<"a"<<endl;
ans=max(ans,premax+querymaxg(i+1,n));
}
}
prepos=0,premax=0,tmpsum=0;
//再走逆时针
for(int i=n;i>=1;i--){
tmpsum+=d[i].val;
if(tmpsum-(2*(c-d[i].dis))>0){
premax=tmpsum-(2*(c-d[i].dis));
ans=max(ans,premax+querymaxf(1,i-1));
// ans=max(ans,premax+baoli(i-1));
}
}
cout<<ans<<endl;
return 0;
}
E - Everything on It
拉面有
n
n
种配料 每种配料可以选择加入到拉面中 也可以不加入 一共 种组合 有人来订购一些拉面
要求:
每种拉面配料不能相同。
每种配料在全部的面中至少出现过两次。
求拉面的方案数 数据范围 n<=3000 n <= 3000
计数问题两种方法——
DP
D
P
和容斥
这题看上去就不太好设状态。。不太能直接
DP
D
P
。
考虑容斥。
f(i)
f
(
i
)
表示 有
i
i
种配料没出现两次 其他配料随意但不重复的方案数。
得到
如果我们得到
f(i)
f
(
i
)
即可
O(n)
O
(
n
)
求出
ans
a
n
s
如何计算
f(i)
f
(
i
)
先考虑
i
i
种配料没出现两次的方案数 其他配料到时候可以添加计算
设 为
j
j
个面中有 个 不会出现两次及以上的配料的个数 即 只出现一次或没出现过
j<=i
j
<=
i
是一个斯特林数递推式
g(i,0)=1
g
(
i
,
0
)
=
1
把
i
i
种配料放到 个碗中 只有不放的情况
g(i,j)=g(i−1,j−1)+g(i−1,j)∗(j+1)
g
(
i
,
j
)
=
g
(
i
−
1
,
j
−
1
)
+
g
(
i
−
1
,
j
)
∗
(
j
+
1
)
其中前半部分是把这个配料放到第
j
j
个碗中,后半部分是把这个配料放到 个碗中或不放。
这里解释一下为什么前后都存在
j
j
个碗但不重复的原因。
因为 这个状态的
所以最后一个碗不会是空碗的。 所以这两个转移一个是碗
j
j
中 只有一种不符合配料, 另一个是 有多种不符合的配料
这样我们可以得到把不符合的 种配料放在前
k
k
个 碗中的方案数。
然后我们考虑把其他配料加进来的方案数。
我们可以直接把这些配料组合 首先有 种组合。
每种组合可以选择放不放在碗中 总计
22n−i
2
2
n
−
i
种组合。
然后前j个碗中的每一个玩 我们可以选择加不加其他配料。直接添加并不会导致重复。
所以是
2(n−i)j
2
(
n
−
i
)
j
种方案。
得到
f(i)=∑g(i)(j)∗22n−i∗2(n−i)j
f
(
i
)
=
∑
g
(
i
)
(
j
)
∗
2
2
n
−
i
∗
2
(
n
−
i
)
j
关于
22n−i
2
2
n
−
i
的计算,由于指数部分过大,我们可以要进行取模。
结论:
an≡an⋅mod(p−1)(modp)
a
n
≡
a
n
·
m
o
d
(
p
−
1
)
(
m
o
d
p
)
证明参考百度。。
计算出
f(i)
f
(
i
)
后 预处理组合数 代入
ans=∑(−1)iC(n,i)f(i)
a
n
s
=
∑
(
−
1
)
i
C
(
n
,
i
)
f
(
i
)
计算答案。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=4005;
typedef long long ll;
ll n,MOD,C[MAXN][MAXN],f[MAXN][MAXN],p[MAXN];
ll qpow(ll a,ll b){
ll ans=1,base=a;
while(b){
if(b&1)(ans*=base)%=MOD;
(base*=base)%=MOD;
b>>=1;
}
return ans;
}
ll qpowt(ll a,ll b){
ll ans=1,base=a;
while(b){
if(b&1)(ans*=base)%=(MOD-1);
(base*=base)%=(MOD-1);
b>>=1;
}
return ans;
}
void get(){
for(ll i=0;i<=n;i++)C[i][0]=1;
for(ll i=1;i<=n;i++){
//c[i][0]=1;
// c[i][i]=1;
for(ll j=1;j<=i;j++){
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
}
for(ll i=0;i<=n;i++){
f[i][0]=1;
for(ll j=1;j<=i;j++){
f[i][j]=f[i-1][j-1]+((f[i-1][j]*(j+1))%MOD);
f[i][j]%=MOD;
}
}
for(ll i=0;i<=n;i++){
ll B=qpow(2,1ll*(n-i));
ll mul=1;
for(ll j=0;j<=i;j++){
p[i]+=f[i][j]*mul;
p[i]%=MOD;
mul*=B;mul%=MOD;
}
p[i]*=qpow(2,qpowt(2,n-i));
p[i]%=MOD;
}
}
//ans=sigma (-1)^i * C(n,i)*f[i];
inline void add(ll &a,ll b){
a+=b;if(a>=b)a-=b;
}
int main(){
scanf("%lld%lld",&n,&MOD);
get();
ll ans=0;
for(ll i=0;i<=n;i++){
ans+=((qpow(-1,i)*C[n][i])%MOD)*p[i];
ans%=MOD;add(ans,MOD);
}
cout<<ans<<endl;
return 0;
}
F - Sweet Alchemy
这次是做甜甜圈的。。强烈谴责四道美食题。
n≤50
n
≤
50
的树,每个点有权值,现要选点(可多次选一个点)使点数尽量多,如下限制:
选的总权值不超过
C≤1e9
C
≤
1
e
9
;
ci
c
i
表示
i
i
选的次数, 表示
i
i
的父亲,那么
D≤1e9
D
≤
1
e
9
是给定常数。
题意要求 儿子选的次数要不严格大于父亲,但不能多出 D D 次。
即 若根选了 次 子树中所有点至少选
xi
x
i
次。
不妨直接把点与子树打包。。
vi
v
i
表示以
i
i
为根子树点权和 表示以
i
i
为根子树点的个数。
题目变成了 有 个物品 每个物品有体积和价值。最大化背包价值。
只不过这是超大容量的背包 显然不能
O(nv)
O
(
n
v
)
做
但我们注意到 价值和物品数量 都很少 均
<=50
<=
50
<script type="math/tex" id="MathJax-Element-26817"><=50</script>。
背包问题的经典错误解法 算性价比然后贪心选择。
错误的原因是 选性价比高的可能因为个体空间较大 从而没充分利用背包容量 有浪费的容量。
我们假设有选了超过
n
n
个性价比低的和少于 个性价比高的。
可以直接替换使答案更优。所以我们应该把一些浪费的空间充分利用,但题目中空间不允许DP 我们可以按照价值来DP。
因为贪心按照
w1/v1>=w2/v2
w
1
/
v
1
>=
w
2
/
v
2
所以
w1v2>=w2v1
w
1
v
2
>=
w
2
v
1
可理解为 把
w1
w
1
个物品2 替换为
w2
w
2
个
v1
v
1
后 体积会变小 价格却不变。
而
w
w
与 同阶。
所以把 每种物品取
min(d,n)
m
i
n
(
d
,
n
)
个放入背包,求出当前价值下最小体积。 剩下的物品贪心即可比较答案。
可以知道最大价值最多为
O(n3)
O
(
n
3
)
多重背包计算
复杂度
O(n4logn)
O
(
n
4
l
o
g
n
)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=2e18;
const int MAXN=55;
ll m[MAXN],p[MAXN],w[MAXN],n,x,d;
// m->v w->w p->fa
ll V[MAXN*100],W[MAXN*100],f[MAXN*MAXN*MAXN];
struct data{
ll v,w,num;
}da[MAXN];
bool cmp(data a,data b){
return (1.0*a.w/a.v)>(1.0*b.w/b.v);
}
int cnt=0;ll maxval=0;
void split(){
int limit,zuoyi=0;
for(int i=1;i<=n;i++){
zuoyi=0;
if(i==1){
limit=n;
da[i].v=m[i],da[i].w=w[i],da[i].num=INF;
}
else {
limit=min(1ll*n,d);
da[i].v=m[i],da[i].w=w[i],da[i].num=d-limit;
}
while(limit){
int p=1<<zuoyi;
if(limit>=p){
limit-=p;if(p*m[i]>x)break;
V[++cnt]=p*m[i];W[cnt]=p*w[i];
maxval+=W[cnt];
}
else {
p=limit;limit=0;if(p*m[i]>x)break;
V[++cnt]=p*m[i];W[cnt]=p*w[i];
maxval+=W[cnt];
}
zuoyi++;
}
}
}
ll solve(ll price,ll volume){
ll ReMain=x-volume,ans=price;
for(int i=1;i<=n;i++){
ll picked=min(da[i].num,ReMain/da[i].v);
ans+=picked*da[i].w;
ReMain-=picked*da[i].v;
}
return ans;
}
int main(){
scanf("%lld%lld%lld",&n,&x,&d);
for(int i=1;i<=n;i++){
ll tmp;
scanf("%lld",&tmp);
m[i]+=tmp;
if(i>1)scanf("%lld",&p[i]);
w[i]=1;
}
for(int i=n;i>=1;i--)w[p[i]]+=w[i],m[p[i]]+=m[i];
split();
// for(int i=1;i<=n;i++)cout<<m[i]<<" "<<w[i]<<endl;
// puts("");
//for(int i=1;i<=cnt;i++)cout<<V[i]<<" "<<W[i]<<endl;
for(int i=0;i<=n*n*n;i++)f[i]=INF;
f[0]=0;
for(int i=1;i<=cnt;i++){
for(int j=n*n*n;j>=W[i];j--){
f[j]=min(f[j],f[j-W[i]]+V[i]);
}
}
// for(int i=0;i<=n*n*n;i++)cout<<i<<":"<<f[i]<<endl;
ll ans=0;
sort(da+1,da+1+n,cmp);
// for(int i=1;i<=n;i++)cout<<da[i].v<<" ";puts("");
for(int i=0;i<=n*n*n;i++){
if(f[i]<=x){
ans=max(ans,solve(i,f[i]));
}
}
cout<<ans<<endl;
return 0;
}