vj题目讲解

M题

题目描述:有n天,第i天上午会进货A[i]件商品,中午的时候会有顾客需要购买B[i]件商品,可以选择卖给他或者不卖。并且如果卖的话需要有足够的库存,问最多能够满足多少个顾客的需求。

贪心思路,很显然可以发现是当能卖时就卖,但是需要注意,如果前面有一个需求很大的人买了,就会导致后面很多人买不到,这个时候就需要用到反悔贪心,可以用大根堆来维护就行。每次看一下之前买的人最大买了多少,如果当前的人出现买不了的情况,就把大根堆的堆顶换成现在这个

void solve()
{
	int n;
	cin >> n;
	priority_queue<pii, vector<pii>, less<pii> > q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> b[i];
	long long tot = 0, cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		tot += a[i];
		if (tot<b[i] && !q.empty() && q.top().first>b[i])
		{
			vis[q.top().second] = false;
			tot += q.top().first;
			q.pop();
			cnt--;
		}
		if (tot >= b[i])
		{
			tot -= b[i];
			q.push({ b[i],i });
			vis[i] = true;
			cnt++;
		}
	}
	cout << cnt << "\n";
	for (int i = 1; i <= n; i++)
		if (vis[i]) cout << i << " ";
	puts("");
}

N题

题意:给定字符串s,字符串t,u初始默认为空,允许两种操作
1.把s字符串第一个字符转移到t字符串最后
2.把t字符串最后一个字符转移到u字符串最后
题目要求最后s,t字符串都为空,问字典序最小的u字符串是多少

贪心策略:当t栈为空,直接执行操作1,当t栈不为空,对于t的栈顶,如果s串中不存在比他字典序更小的单词,就执行操作2,否则的话就执行操作1

void solve()
{
	cin >> s;
	int n = s.size();
	mmin[n - 1] = s[n - 1];
	for (int i = n - 2; i >= 0; i--)
	{
		mmin[i] = min(mmin[i + 1], s[i]);
	}

	stack<char> ans;

	for (int i = 0; i < n - 1; i++)
	{
		ans.push(s[i]);
		while (!ans.empty() && ans.top() <= mmin[i + 1]) cout << ans.top(), ans.pop();
	}

	ans.push(s[n - 1]);
	while (!ans.empty())
	{
		cout << ans.top();
		ans.pop();
	}

}

O题

题意:给定n种木棍,每种木棍的个数为a[i],长度为
2i,求用这些木棍最多可以组成多少个三角形

题解:很明显,由于三角形两边之和大于第三边,所以三角形只能有两种情况,等边三角形( 2i,2i,2i), 等腰三角形(2i,2i+1,2i+1)。
然后我们贪心的去选择,要构造最多的三角形,即要用掉最多的边,可以发现等腰三角形中的底边都是较短的边,贪心策略是优先构造等腰三角形再构造等边三角形。

代码


void solve() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] %= k;
    }
    priority_queue<int, vector<int>, greater<int> >q;
    for (int i = 1; i <= n; i++) {
        if (a[i] > a[i - 1]) {
            q.push(a[i] - a[i - 1]);
            ans += q.top();
            q.pop();
        }
        if (a[i] < a[i - 1]) {
            q.push(a[i] + k - a[i - 1]);
        }
    }
    printf("%lld\n", ans);
    ans = 0;
}

P题

题意:有n个章鱼,第i只章鱼初始健康值为a[i],每次操作可以将[l,r]内的章鱼健康值减1,一旦章鱼的健康值达到0,就会再生到k。求最小操作数,使每只章鱼的健康值为k

思路:发现如果没有模k的话这就是一个经典的差分,目标序列为 A A A,差分序列为 B B B,而如果要考虑模k的话,我们可以预先对目标序列 A A A进行若干次区间加k,求在所有可以得到的 A ′ A' A序列中答案的最小值
而A序列上的区间加可以视作B序列上两端点的加减。问题就可以转化为给定序列B,可以进行若干次操作,每次操作可以选定 x , y x,y x,y使 B x − > B x + k , B y − − > B y − k B_x -> B_x +k,B_y -->B_y -k Bx>Bx+k,By>Byk
从前往后考虑,假设当前考虑到第x个数,分两种情况讨论:

  • B x < 0 B_x <0 Bx<0 :此时我们将 B x B_x Bx 放入一个待选集合,集合中存储的可能是作为操作前一项,与后面元素一起操作的数。
  • B x > = 0 B_x>=0 Bx>=0:此时我们从待选集合中贪心的选出最小数,如果与当前数进行一次操作能使答案更优,我们就进行操作,可以发现操作后当前数一定为负数,将他放入待选集合。

用优先队列维护上述过程,时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

void solve()
{
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> v[i], v[i] %= k;
	for (int i = n; i >= 1; i--) v[i] -= v[i - 1];
	priority_queue<int, vector<int>, greater<int>> q;
	long long ans = 0;
	for (int i = 1; i <= n; i++)
	{
		if (v[i] < 0) q.push(v[i]);
		else
		{
			if (q.empty()) { ans += v[i]; continue; }
			int now = q.top(); q.pop();
			if (now + k < v[i]) ans += now + k, v[i] -= k, q.push(v[i]);
			else ans += v[i], q.push(now);
		}
	}
	cout << ans << "\n";
}

Q题

题意:有n个竹子,第i个竹子初始高度为hi,每一天可以选择k根竹子使他们高度减少p,减少之后的h[i]=min( h[i]-p , 0 )。求 m 天后最高竹子的最小值

题解:像这种最小化最大值的问题可以想到二分答案。代码实现的时候会发现正着写很麻烦,可以尝试倒着写。即每次操作改为将每个h[i]改为h[i]-a[i]同时保证h[i]-a[i]>=0, 然后是k次选择一个数h[i]改为h[i]+p,要求最终得到的每个h[i]都不小于给定的h[i]

选择的过程可以使用贪心

  • 每次选择最容易导致h[i]-a[i]<0的位置加上p,如果选完还是导致小于0则是不合法
  • 如果不存在可能导致h[i]-a[i]<0的位置,则直接判断剩下的次数是否足够使最终所有的h[i]均大于等于给定的n个数

代码:

bool check(LL o) {
	priority_queue< pair<int, int> > q;
	for (int i = 1; i <= n; i++) {
		if (h[i] + m * a[i] <= o) continue;
		q.push(make_pair(-(o / a[i]), i)), c[i] = 0;
	}
	int cnt = 0;
	for (int i = 1; q.size() && i <= m; i++)
		for (int j = 1; q.size() && j <= k; j++) {
			if (-q.top().first < i) return false;
			int x = q.top().second;
			q.pop();
			LL w = (o + (++c[x]) * p) / a[x];
			if (w < m) q.push(make_pair(-w, x));
			++cnt;
		}
	for (int i = 1; i <= n; i++) {
		LL w = o + c[i] * p - m * a[i];
		if (h[i] <= w) continue;
		w = (h[i] - w - 1) / p + 1;
		if (cnt + w > m * k) return false;
		cnt += w;
	}
	return true;
}

void solve()
{
	cin >> n >> m >> k >> p;
	for (int i = 1; i <= n; i++) {
		cin >> h[i] >> a[i];
		l = max(l, a[i]), r = max(r, h[i] + m * a[i]);
	}
	while (l < r) {
		LL o = (l + r) >> 1;
		if (check(o)) r = o;
		else l = o + 1;
	}
	cout << l;
}

R题

题意:给定n条端点在圆上的线段,用最少条线段来切割,使得圆上的每条线段都被切割

思路:可以发现,一条线被切断后,这一刀与圆的交点一定在这条线的两侧,对应到链上,我们称刀与圆的交点为断点,被切断的线对应的区间里面一定有一个断点,外面有一个断点。这时候问题就可以转化为选尽量少的断点,让连续的n个区间每个区间里面都有一个断点,外面都有一个断点

我们可以找一个最短的区间,在区间内部枚举一个断点,记录第一个断点的位置,然后往后找,如果枚举到一个区间断点 ,他对应的区间的左端点大于第一个断点,则说明这个断点已经不在这个端点对应的区间里面了。这样就只能在这个区间里再开一个断点

我们记最短的区间长度为len,枚举起点要len次,每次往后枚举+len ,因为len是最短的距离,所以不会跳过一整个区间,这样时间复杂度是 O ( N ) O(N) O(N)

const int maxn = 4e5 + 10;

int n;
int to[maxn << 1];

struct node {
	int l, r;
}w[maxn << 1];

int dis(int l, int r) {
	return min(r - l, 2 * n + l - r);
}

void solve()
{
	cin >> n;
	int xx = 0, md = 0x3f3f3f3f;
	for (int i = 1, l, r; i <= n; i++) {
		cin >> l >> r;
		if (l > r) swap(l, r);
		w[i].l = l; w[i].r = r;
		w[i + n].l = l + 2 * n; w[i + n].r = r + 2 * n;
		to[r] = l; to[l + 2 * n] = r; to[r + 2 * n] = l + 2 * n;
		if (dis(l, r) < md) md = dis(l, r), xx = i;
	}
	int Start, End;
	if (w[xx].r - w[xx].l == md) Start = w[xx].l, End = w[xx].r;
	else Start = w[xx].r, End = w[xx].l + 2 * n;
	int  len = dis(Start, End);
	int ans = 0x3f3f3f3f;
	for (int i = Start; i <= End; i++) {
		int last = i, sum = 0;
		for (int j = i + len; j < i + 2 * n; j++)
			if (to[j] > last) last = j, sum++, j += len;
		ans = min(ans, sum);
	}
	printf("%d\n", (ans / 2) + 1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值