滑水
签到题
很明显,我们应该堵住几个最大的洞。 让我们先对它们进行排序。 之后,让我们迭代阻塞孔的数量,保持非阻塞孔大小的总和 S。使用值 S 很容易计算来自第一个孔的流量是否足够大。 只是在流量足够大的时候输出第一时间阻塞管道的数量。 复杂度为 O(n)。
#include<bits/stdc++.h>
using namespace std;
int n, a, b;
double x[100005];
int main()
{
cin >> n >> a >> b;
double sum = 0;
for (int i = 1; i <= n; i++)
{
cin >> x[i];
sum += x[i];
}
sort(x + 2, x + n + 1);
if (x[1] * a / sum >= b)cout << 0;
else
for (int i = n; i >= 2; i--)
{
sum -= x[i];
if (x[1] * a / sum >= b) {
cout << n - i + 1;
break;
}
}
}
写代码
d
p
[
i
]
[
j
]
[
k
]
表
示
前
i
个
程
序
员
写
到
第
j
行
共
有
k
个
b
u
g
的
方
案
数
dp[i][j][k]表示前i个程序员写到第j行共有k个bug的方案数
dp[i][j][k]表示前i个程序员写到第j行共有k个bug的方案数
其实仔细想想这就是一个二维费用01背包,行数->容量 bug数->质量 ,所以可以进行第一维优化
注意最终答案是少于k个bug所以全部加起来得到答案,同时要注意统计方案数的初始值设定。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m, b, mod;
int a[505],dp[505][505];
int main()
{
cin >> n >> m >> b >> mod;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for(int j=1;j<=m;j++)
for (int k = b; k >=0; k--)
{
if(k>=a[i])dp[j][k] = (dp[j][k] + dp[j - 1][k - a[i]]) % mod;
}
}
ll res = 0;
for (int i = 0; i <= b; i++)
res = (res + dp[m][i]) % mod;
cout << res << endl;
}
获取硬币
首先,观察每个玩家对于他们的路径只有 m 个选项——进入哪一列。
让我们考虑 Bob 对 Alice 选择的策略的反应。 解决这个问题的最简单方法是查看 Alice 路径的图片。
我们会发现Alice一定会在某一列往下走,然后将字段分成两个独立的部分——第一行的后缀和第二行的前缀。 鲍勃不能同时从他们俩手中抢走硬币。 但是,他可以完全抓住它们中的任何一个。 因此,他的最佳路径将是这两个选项之一。
您可以预先计算一些前缀总和,并能够在给定 Alice 的路径的情况下获得 Bob 的分数。 Alice 有 m 条可能的路径,枚举Alice从哪里往下走,然后答案就是所有方案中的最小值。
#include<bits/stdc++.h>
using namespace std;
int mp[3][100005];
int dp[3][100005];
void solve()
{
int n;
cin >> n;
for(int i=1;i<=2;i++)
for (int j = 1; j <= n; j++)
{
cin >> mp[i][j];
mp[i][j] += mp[i][j - 1];
}
int ans = 0x3f3f3f3f;
for (int i = 1; i <= n; i++)
{
int a = mp[2][i-1],b=mp[1][n]-mp[1][i];
//cout << i << " " << a << " " << b << endl;
ans = min(ans, max(a, b));
}
cout << ans << endl;
}
int main()
{
int t; cin >> t;
while (t--)
{
solve();
}
}
煮包子
多重背包,我们可以把c0的面粉当作另一种物品,当循环前面的糕点时,就可以正确地计算出答案。我们定义(f[i][j]:)前i种糕点,j个体积的最大收益,我们的(DP转移方程式:f[i][j] = max(f[i][j], f[i - 1][j - k * b[i]] + k * d[i]),并且k * b[i] <= a[i]且k * c[i] <= j)。我们把剩下的不加馅料的面粉也计算进来,就可以求出整个最大收益了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
struct Node
{
int a, b, c, d;
}p[N];
int f[15][1500];
int main()
{
int n, m, c0, d0;
scanf("%d%d%d%d", &n, &m, &c0, &d0);
for (int i = 1; i <= m; ++i) scanf("%d%d%d%d", &p[i].a, &p[i].b, &p[i].c, &p[i].d);
for(int i = 1; i <= m; ++i)
for (int j = 1; j <= n; ++j)
{
for (int k = 0; k * p[i].b <= p[i].a && k * p[i].c <= j; ++k)
{
f[i][j] = max(f[i][j], f[i - 1][j - k * p[i].c] + k * p[i].d);
}
}
for (int j = 1; j <= n; ++j)
{
for (int k = 0; k * c0 <= j; ++k)
{
f[m + 1][j] = max(f[m + 1][j], f[m][j - k * c0] + k * d0);
}
}
printf("%d", f[m + 1][n]);
return 0;
}
大盗
01背包
题目要问的,是 kk 个物品最多能组成多少种不同的权值之和(每个数量不限)。
正着求当然很麻烦,但是反着解相对来说就简单多了。
所以我们可以将题目转化为:
求凑某个权值和最少要多少物品,如果最少个数比 k 要大,则这个权值和就无法凑出。
题目分析到这里,就不难看出是背包 dp 了。
状态转移
d
p
[
i
]
表
示
凑
出
权
值
和
为
i
最
少
需
要
多
少
件
物
品
。
dp[i]表示凑出权值和为 i 最少需要多少件物品。
dp[i]表示凑出权值和为i最少需要多少件物品。
这样大于k的权值一定无法拼出来
但是最少需要这么多件不一定能满足有k个组成可能会比k少,所以需要再处理一下
解决恰好k数量问题
这里做一个简单的优化:
我们让权值最小的物品的权值为 0,其余的物品权值都减去最小的权值。
这样少于k的时候可以用权值最小的物品来代替,一定可以凑出来恰好k个物品满足的条件,最后输出的时候需要复原本身的值.
时间复杂度是1e9,所以我们再优化一下循环的开始和结束,时限给的是10s,所以刚好能过
#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
int dp[N],a[N];
int main()
{
int n, m;
cin >> n >> m;
int mi = 0x3f3f3f3f,mx=0;
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++)
{
cin >> a[i];
mi = min(a[i], mi);
mx = max(a[i], mx);
}
dp[0] = 0;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++)
{
a[i] -= mi;
}
for (int i =1; i <= m*mx; i++)// 01 背包
{
for (int j = 1; j <= n; j++)
{
if (i >= a[j])dp[i] = min(dp[i], dp[i - a[j]] + 1);
else break;
}
}
for (int i =0; i <= m*mx; i++)
{
if (dp[i] <= m)cout << i+m*mi << " ";//减去k个mi所以答案要加上这么多
}
cout << endl;
}
时间表
注意到数据量很小,可以想到进行三层循环都不会T,考虑dp。
定义状态:dp[i][j]表示前i天,逃了j节课,前i天呆在学校的最少时间。
还定义一个辅助状态:mn[i][j]表示第i天逃j节课,第i天呆在学校的最少时间。
则状态转移方程:dp[i][j]=min(dp[i][j],dp[i-1][j-c]+min[i][c])。其中0<=c<=j。
枚举c,c是第i天准备要逃的课数,则前i-1天逃的课数为j-c,通过这个来迭代。
mn的求法:
先预处理出每天的“1”的下标,假设存在vector中,对于第i天,枚举起始下标和终止下标,设为j和l,表示逃掉j前面的课(不包括j)和逃掉l后面的课(不包括l)。
则公式为:mn[i][vec[i].size()-l-1+j]=min(mn[i][vec[i].size()-l-1+j],vec[i][l]-vec[i][j]+1);
vec[i]表示第i天的“1”的个数,里面存的是下标。vec[i].size()-l-1+j是逃掉的课数,vec[i][l]-vec[i][j]+1则是在当前这个策略下,第i天要呆在学校的时间。
最后dp[n][k]就是答案。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=510;
int n,m,k;
int dp[MAXN][MAXN],mn[MAXN][MAXN];
char str[MAXN][MAXN];
vector<int> vec[MAXN];
int main()
{
while(scanf("%d %d %d",&n,&m,&k)!=EOF)
{
for(int i=1;i<=n;i++) scanf("%s",&str[i][1]);
memset(mn,0x3f,sizeof(mn));
for(int i=1;i<=n;i++)
{
vec[i].clear();
for(int j=1;j<=m;j++)
{
if(str[i][j]=='1') vec[i].push_back(j);
}
for(int j=vec[i].size();j<=max(m,k);j++)
mn[i][j]=0;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<vec[i].size();j++)
{
for(int l=j;l<vec[i].size();l++)
mn[i][vec[i].size()-l-1+j]=min(mn[i][vec[i].size()-l-1+j],vec[i][l]-vec[i][j]+1);
}
}
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<=k;i++) dp[0][i]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k;j++)
{
for(int l=0;l<=j;l++)
dp[i][j]=min(dp[i][j],dp[i-1][j-l]+mn[i][l]);
}
}
printf("%d\n",dp[n][k]);
}
return 0;
}