FJUT 2022 第3周周赛

滑水

签到题
很明显,我们应该堵住几个最大的洞。 让我们先对它们进行排序。 之后,让我们迭代阻塞孔的数量,保持非阻塞孔大小的总和 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]ijkbug
其实仔细想想这就是一个二维费用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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值