Educational Codeforces Round 94 部分题解

5 篇文章 0 订阅
2 篇文章 0 订阅

B. RPG Protagonist

题意

给两个容量分别为 p p p f f f的背包,和 c n t s cnt_s cnts个重量为 s s s c n t w cnt_w cntw个重量为 w w w,价值都为 1 1 1的物品,求背包能装的物品的最大价值。

思路

假设 s ≤ w s\leq w sw(如果 s > w s > w s>w可以交换两物品),枚举第一个背包中重量为 s s s的物品的个数,剩下的重量为 s s s的物品尽可能地放进第二个背包,最后两个背包剩余的空间尽可能地放重量为 w w w的物品。

代码

#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;

#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)

int m1, m2, cnts, cntw, s, w;

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && (ch ^ '-'));
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
	return res * p;
}

int main()
{
	int kase;
	kase = getint();
	while (kase--)
	{
		m1 = getint(); m2 = getint();
		cnts = getint(); cntw = getint();
		s = getint(); w = getint();
		if (w < s) swap(s, w), swap(cnts, cntw);
		int ans = 0;
		fo(i, 0, cnts)
			if (i * s <= m1)
			{
				int cntw1 = min((m1 - i * s) / w, cntw);
				int cnts2 = min(m2 / s, cnts - i);
				int cntw2 = min((m2 - cnts2 * s) / w, cntw - cntw1);
				ans = max(ans, i + cntw1 + cnts2 + cntw2);
			}
			else break;
		printf("%d\n", ans);
	}
	return 0;
}

C. Binary String Reconstruction

题意

对于一个 01 01 01 w w w和给定的 x x x 01 01 01 s s s s i = w i − x ∣ w i + x s_i=w_{i-x}|w_{i+x} si=wixwi+x(当 i − x < 0 i-x<0 ix<0 w i − x = 0 w_{i-x}=0 wix=0 i + x i+x i+x同理),现已知串 w w w x x x求串 s s s,若 s s s不存在则输出 − 1 -1 1

思路

根据 s s s的定义,当 s i = 0 s_i=0 si=0时, w i − x = w i + x = 0 w_{i-x}=w_{i+x}=0 wix=wi+x=0,先把所有 w w w串上述位置填 0 0 0,其他位置填 1 1 1,最后检查 w w w是否合法即可。

代码

#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <iostream>
using namespace std;

#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)

const int maxn = 1e5 + 5;

int n, x;
char s[maxn], w[maxn];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && (ch ^ '-'));
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
	return res * p;
}

bool check()
{
	fo(i, 1, n)
	{
		char ch;
		ch = i - x >= 1 && w[i - x] == '1' || i + x <= n && w[i + x] == '1'? '1' : '0';
		if (s[i] != ch) return false;
	}
	return true;
}

int main()
{
	int kase;
	kase = getint();
	while (kase--)
	{
		scanf("%s", s + 1);
		n = strlen(s + 1);
		x = getint();
		memset(w, 0, sizeof(w));
		w[n + 1] = '\0';
		fo(i, 1, n)
			if (s[i] == '0')
			{
				if (i - x >= 1) w[i - x] = '0';
				if (i + x <= n) w[i + x] = '0';
			}
		fo(i, 1, n)
			if (!w[i]) w[i] = '1';
		if (check()) printf("%s\n", w + 1);
			else printf("-1\n");
	}
	return 0;
}

D. Zigzags

题意

给一个长度为 n n n的序列 { a i } \{a_i\} {ai},求满足以下条件的四元组 ( i , j , k , l ) (i,j,k,l) (i,j,k,l)的个数:

  • 1 ≤ i < j < k < l ≤ n 1 \leq i < j < k < l\leq n 1i<j<k<ln;
  • a i = a k a_i=a_k ai=ak a j = a l a_j=a_l aj=al;

思路

枚举 k , l k,l k,l,用桶统计所有 ( a i , a j ) (a_i,a_j) (ai,aj)的出现次数,累加到答案, k k k每向右移动一位,更新桶。

代码

#include <bits/stdc++.h>
using namespace std;

#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)

typedef long long ll;

const int maxn = 3e3 + 5;

int n;
int a[maxn], cnt[maxn * maxn];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && (ch ^ '-'));
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
	return res * p;
}

inline int getnum(int a, int b)
{
	return (a - 1) * n + b;
}

int main()
{
	int kase;
	kase = getint();
	while (kase--)
	{
		n = getint();
		fo(i, 1, n) a[i] = getint();
		memset(cnt, 0, sizeof(cnt));
		ll ans = 0;
		fo(k, 1, n)
		{
			fo(l, k + 1, n)
				ans += cnt[getnum(a[k], a[l])];
			fo(i, 1, k - 1) cnt[getnum(a[i], a[k])]++;
		}
		printf("%lld\n", ans);
	}
	return 0;
}

E. Clear the Multiset

题意

给定一个长度为 n n n序列 { a i } \{a_i\} {ai},每次可以执行以下两种操作之一:

  1. 选定一个区间 [ l , r ] [l,r] [l,r],所有 a k ( l ≤ k ≤ r ) a_k(l \leq k \leq r) ak(lkr)必须满足 a k > 0 a_k>0 ak>0 a k = a k − 1 a_k=a_k-1 ak=ak1
  2. 选定两个数 i i i x ( x ≤ a i ) x(x \leq a_i) x(xai) a i = a i − x a_i=a_i-x ai=aix

求将序列清零的最少操作数。

思路

解法一

dp,设状态 f [ i ] [ j ] f[i][j] f[i][j]为序列的前 i i i个数清零,且位置 i i i上执行了 j ( j ≤ a i ) j(j \leq a_i) j(jai)次操作一,所需的最少操作数。由于操作顺序不影响结果,我们先考虑执行操作一,再考虑执行操作二。
考虑转移,状态 f [ i ] [ j ] f[i][j] f[i][j]可以从状态 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]转移而来,下面对 k k k进行分类讨论:

  1. 0 ≤ k ≤ j 0 \leq k \leq j 0kj,状态 f [ i ] [ j ] f[i][j] f[i][j]的前 k k k次操作一和状态 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]一起进行,后 j − k j-k jk次操作单独进行,若 a i − j > 0 a_i-j>0 aij>0则另需 1 1 1次操作二将 a i a_i ai清零,所以对 f [ i ] [ j ] f[i][j] f[i][j]的贡献是 f [ i − 1 ] [ k ] + j − k + [ a i − j > 0 ] f[i-1][k]+j-k+[a_i-j>0] f[i1][k]+jk+[aij>0](中括号的值为真则返回值为 1 1 1,否则为 0 0 0)。
  2. j < k ≤ m a x { a i } j < k \leq max\{a_i\} j<kmax{ai},状态 f [ i ] [ j ] f[i][j] f[i][j] j j j次操作都可以和状态 f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]一起进行,若 a i − j > 0 a_i-j>0 aij>0则另需 1 1 1次操作二将 a i a_i ai清零,所以对 f [ i ] [ j ] f[i][j] f[i][j]的贡献是 f [ i − 1 ] [ k ] + [ a i − j > 0 ] f[i-1][k]+[a_i-j>0] f[i1][k]+[aij>0]

综上
f [ i ] [ j ] = { f [ i − 1 ] [ k ] + j − k + [ a i − j > 0 ] 0 ≤ k ≤ j f [ i − 1 ] [ k ] + [ a i − j > 0 ] j < k ≤ m a x { a i } f[i][j]=\begin{cases} f[i-1][k]+j-k+[a_i-j>0] & 0 \leq k \leq j \\ f[i-1][k]+[a_i-j>0] & j < k \leq max\{a_i\} \end{cases} f[i][j]={f[i1][k]+jk+[aij>0]f[i1][k]+[aij>0]0kjj<kmax{ai}
该dp可以进一步优化,有效的 j j j的取值为 { a i } \{a_i\} {ai}中的数,只枚举有效的 j j j,并且维护 f [ i − 1 ] [ k ] − k f[i-1][k]-k f[i1][k]k的前缀最小值, f [ i − 1 ] [ k ] f[i-1][k] f[i1][k]的后缀最小值,即可达到 O ( 1 ) O(1) O(1)转移,总时间复杂度为 O ( n 2 ) O(n^2) O(n2)
我们又可以发现,若对一个区间 [ l , r ] [l,r] [l,r]整体减 x x x,那么, x x x是区间 [ l , r ] [l,r] [l,r]中的最小值,换句话说,区间 [ l , r ] [l,r] [l,r]中存在一个数只执行操作一清零,且该数为 x x x
据此,我们从新定义dp状态,设 f [ i ] [ 0 ] f[i][0] f[i][0]为前 i i i个数清零,且 a i a_i ai只执行操作一清零, f [ i ] [ 1 ] f[i][1] f[i][1]为前 i i i个数清零, a i a_i ai执行操作二清零。
根据定义,只执行操作二清零
f [ i ] [ 1 ] = m i n { f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] } + [ a i > 0 ] f[i][1]=min\{f[i-1][0],f[i-1][1]\}+[a_i>0] f[i][1]=min{f[i1][0],f[i1][1]}+[ai>0]
对于状态 f [ i ] [ 0 ] f[i][0] f[i][0],有三种情况:

  1. 单独使用 a i a_i ai次操作一,此时对 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ i − 1 ] [ 1 ] + a [ i ] f[i-1][1]+a[i] f[i1][1]+a[i]
  2. 共用 a i a_i ai次操作一,即 a i a_i ai为上述 x x x,枚举操作一的左边界 j ( a i ≤ a j ) j(a_i \leq a_j) j(aiaj) j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a i min\{a_{j+1} \cdots a_{i-1}\} \geq a_i min{aj+1ai1}ai a j + 1 ⋯ a i a_{j+1} \cdots a_{i} aj+1ai a j a_j aj共用 a i a_i ai次操作一,然后对 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1]中的数用操作二清零,所以此时对 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ j ] [ 0 ] + i − j − 1 f[j][0]+i-j-1 f[j][0]+ij1
  3. 共用 a j a_j aj次操作一,单独使用 a i − a j ( a i > a j ) a_i-a_j(a_i>a_j) aiaj(ai>aj)次操作一,同理 j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a j min\{a_{j+1} \cdots a_{i-1}\} \geq a_j min{aj+1ai1}aj,同理对 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1]中的数用操作二清零,此时第 f [ i ] [ 0 ] f[i][0] f[i][0]的贡献是 f [ j ] [ 0 ] + i − j − 1 + a i − a j f[j][0]+i-j-1+a_i-a_j f[j][0]+ij1+aiaj

可能的疑惑:

  • 为什么在1.中的贡献不是 m i n { f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] } + a [ i ] min\{f[i-1][0],f[i-1][1]\}+a[i] min{f[i1][0],f[i1][1]}+a[i]?因为 f [ i − 1 ] [ 0 ] + a [ i ] f[i-1][0]+a[i] f[i1][0]+a[i]不可能是最优解,显然 a i a_i ai a i − 1 a_{i-1} ai1共用部分操作一会更优,在2.和3.中已经考虑过。
  • 为什么在2.中 j j j满足 m i n { a j + 1 ⋯ a i − 1 } ≥ a i min\{a_{j+1} \cdots a_{i-1}\} \geq a_i min{aj+1ai1}ai?因为要保证 a i a_i ai a j a_j aj在同一个操作一的区间内。3.中同理。
  • 为什么在2.中只用操作二清零 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1]中的数?假若存在一组更优的解, [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i1]中的数使用操作一,那么该操作一的右边界可以扩展到 i i i,该解在后两种情况中已经考虑,所以只考虑用操作二。

综上
f [ i ] [ 1 ] = m i n { f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] } + [ a i > 0 ] f[i][1]=min\{f[i-1][0],f[i-1][1]\}+[a_i>0] f[i][1]=min{f[i1][0],f[i1][1]}+[ai>0]
f [ i ] [ 0 ] = m i n { f [ j ] [ 0 ] + i − j − 1 a i ≤ a j f [ j ] [ 0 ] + i − j − 1 + a i − a j a i > a j f[i][0]=min\begin{cases} f[j][0]+i-j-1 & a_i \leq a_j\\ f[j][0]+i-j-1+a_i-a_j & a_i>a_j \end{cases} f[i][0]=min{f[j][0]+ij1f[j][0]+ij1+aiajaiajai>aj
对于 f [ i ] [ 0 ] f[i][0] f[i][0]的两种情况都有 1 ≤ j < i 1 \leq j < i 1j<i m i n { a j + 1 ⋯ a i − 1 } ≥ m i n { a i , a j } min\{a_{j+1} \cdots a_{i-1}\} \geq min\{a_i,a_j\} min{aj+1ai1}min{ai,aj}
直接转移的时间复杂度是 O ( n 2 ) O(n^2) O(n2),可以再优化一下。
考虑条件 m i n { a j + 1 ⋯ a i − 1 } ≥ m i n { a i , a j } min\{a_{j+1} \cdots a_{i-1}\} \geq min\{a_i,a_j\} min{aj+1ai1}min{ai,aj},可以发现对于两个可能的转移 j 1 j_1 j1 j 2 j_2 j2,假设 j 1 < j 2 j_1<j_2 j1<j2,若 a [ j 1 ] > a [ j 2 ] a[j_1]>a[j_2] a[j1]>a[j2],则 j 1 j_1 j1是不满足以上条件的,即一个合法的转移集合内所有数满足:对于任意 j 1 < j 2 j_1<j_2 j1<j2,都有 a [ j 1 ] ≤ a [ j 2 ] a[j_1] \leq a[j_2] a[j1]a[j2],对于 a [ j 1 ] = a [ j 2 ] a[j_1]=a[j_2] a[j1]=a[j2]只保留 j 2 j_2 j2,因为 j 2 j_2 j2已经考虑了从 j 1 j_1 j1转移过来的情况。因此可以用单调栈优化转移,从栈顶开始,将满足 a i ≤ a j a_i \leq a_j aiaj j j j出栈,同时更新 f [ i ] [ 0 ] f[i][0] f[i][0] a i ≤ a j a_i \leq a_j aiaj的情况),用维护 f [ j ] [ 0 ] − j − a j f[j][0]-j-a_j f[j][0]jaj的前缀最小值(从栈底到某元素)更新 f [ i ] [ 0 ] f[i][0] f[i][0] a i > a j a_i>a_j ai>aj的情况),然将 i i i入栈,求出对应的前缀最小值。
到此,时间复杂度降为 O ( n ) O(n) O(n)

解法二

对于序列 { a i } \{a_i\} {ai},两种处理方法:

  1. 全使用操作二清零。
  2. 对整个序列减去最小值 a x a_x ax,递归处理 [ 1 , x ) [1,x) [1,x) ( x , n ] (x,n] (x,n]

两种处理方法取最小值即可。
时间复杂度 O ( n 2 ) O(n^2) O(n2)
另:对于2.可用rmq求法将时间复杂度优化到 O ( n l o g n ) O(nlogn) O(nlogn)甚至 O ( n ) O(n) O(n)

代码

解法一

#include <bits/stdc++.h>
using namespace std;

#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)

const int maxn = 5e3 + 5, inf = 1e9;

struct data{int pos, mval;};
stack<data> st;
int n;
int a[maxn];
int f[maxn][2];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && (ch ^ '-'));
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
	return res * p;
}

int main()
{
	n = getint();
	fo(i, 1, n) a[i] = getint();
	f[1][0] = a[1]; f[1][1] = 1;
	st.push((data){1, f[1][0] - a[1] - 1});
	fo(i, 2, n)
	{
		f[i][1] = min(f[i - 1][0], f[i - 1][1]) + (a[i] > 0);
		f[i][0] = f[i - 1][1] + a[i];
		while (!st.empty() && a[st.top().pos] >= a[i])
		{
			data tp = st.top(); st.pop();
			f[i][0] = min(f[i][0], f[tp.pos][0] + i - tp.pos - 1);
		}
		if (!st.empty())
		{
			f[i][0] = min(f[i][0], a[i] + i - 1 + st.top().mval);
			st.push((data){i, min(f[i][0] - a[i] - i, st.top().mval)});
		}
		else st.push((data){i, f[i][0] - a[i] - i});
	}
	printf("%d\n", min(f[n][0], f[n][1]));
	return 0;
}

解法二

#include <bits/stdc++.h>
using namespace std;

#define fo(i, x, y) for (int i = x; i <= y; ++i)
#define fd(i, x, y) for (int i = x; i >= y; --i)

const int maxn = 5e3 + 5, inf = 1e9;

int n;
int a[maxn];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && (ch ^ '-'));
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = (res << 3) + (res << 1) + (ch ^ 48), ch = getchar();
	return res * p;
}

int solve(int l, int r)
{
	if (l > r) return 0;
	if (l == r) return a[l] > 0;
	int mi = -1, tmp, cnt = 0;
	fo(i, l, r)
	{
		if (mi == -1 || a[i] < a[mi]) mi = i;
		cnt += a[i] > 0;
	}
	tmp = a[mi];
	fo(i, l, r) a[i] -= tmp;
	return min(tmp + solve(l, mi - 1) + solve(mi + 1, r), cnt);
}

int main()
{
	n = getint();
	fo(i, 1, n) a[i] = getint();
	printf("%d\n", solve(1, n));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值