20250721补题

20250721补题

T1

规律题,忽略

T2

题目大意

\(n\)个装水的盆子,排成一排。而对盆子的访问顺序为\(1,2,3,...,n-2,n-1,n,n-1,n-2,...,3,2,1\),并以此循环。每当访问到一个盆子时,若盆子已经积满了水, 则将水喝完,否则不管。但是,由于各个盆子所放位置不同,收集水的速度不同。对于编号为\(i\)的盆子,需要\(a_i\)天时间收集满灵水,也即若在第\(d\)天喝完了所有水,下次最早能够在第\(d+a_i+1\)天收集满。初始所有盆子都收集满。现在有\(m\)个询问,第\(i\)个询问需要你求出在第\((i-1) * (2n-1) +1\)到第\(i*(2n-1)\)天中有多少天能够喝到水。

数据范围:\(\sum n,\sum m \le 1e6\)

解题思路

约定:对于\(1,2,...,n-1,n\),我们将此区间称为\(A\),否则称为\(B\)

考虑寻找一下规律,我们能够发现实际上对于每一个\(i\),只可能出现以下三种情况

1.\(A->A->A\)

2.\(A->B->B->B\)

3.\(A->B->A->B->A->B...\)

并且,对于以上三种情况,其跳的轮数也是有规律的,为

1.\(k,k,k,...\)

2.\(k-1,k,k,k,...\)

3.\(k-1,k,k-1,k,k-1,k,...\)

而要得出以上三种情况,对于一名OIer,证明略显困难,可以使用打表或者手模一下样例解决

并且,这种方式看似会超时,实则不然,因为我们可以对于三种情况,把同样\(k\)一起跳掉,实际上时间复杂度是正确的

示例代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
const int N = 1e6 + 10;
ll n,m;
ll ans[N],a[N],b[N],c[N];
void init(){
	for(int i=0;i<=m;i++) a[i] = b[i] = c[i] = ans[i] = 0;
    return ;
}
void solve()
{
    init();
    cin >> n >> m;
    for(int i=1;i<=n;i++){
        ll x;
        cin >> x;
        ll d = x / (n + n - 1);
        if(d > m){
            ans[1] ++ ;
            continue;
        }
        x = (x % (n + n - 1)) + 1;
        if(x > n + n - i - i || i == n) a[d] ++ ;
        else if(x > i + i - 1) b[d] ++ ;
        else c[d] ++ ;
    }
    for(int k=0;k<=m;k++){
        for(int i=1;i<=m;i += (k + 1)) ans[i] += a[k] ;
        for(int i=1,o=0;i <= m;i += k + o, o = 1) ans[i] += b[k] ;
        for(int i=1,o=0;i<=m;i += k + o,o ^= 1) ans[i] += c[k] ;
    }
    for(int i=1;i<=m;i++) cout << ans[i] << ' ';
    cout << "\n";
    return ;
}
int main(){
	IOS;
    File("collect");
    int T;
    cin >> T;
    while(T -- ) solve();
    return 0;
}

T3

题目大意

小 A 来到一个商店,商店内有\(n\)件物品,每件物品有一个售价\(A_i\),小A是一个奸商,因此他能够把每件物品以\(B_i\)的价格卖出。而由于小A背包空间有限,他只能买走恰好一件商品。而小A每次购买之后,小B将有可能将小A的物品摧毁,此时小A必须继续购买,并且商店不会返还购买这件物品的钱。小B至多可以摧毁\(m\)件物品 (小A知道这件事情)。小A的目标是最大化自己的收益。小B的目标是最小化小 A 获得的收益,即卖出的商品所得减去购买所有物品的总花费。 注意到任何时刻小B都可以选择不将小A手中的物品摧毁,这样小A就必须立刻带着这件物品离开商店。你要求出小A最终能获得的最大收益。数据范围:\(n \le 10^5,m < n , \sum n \le 3\times10^5\)

解题思路

首先需要明确,这个过程可视为小A先手,小B后手(也就是小B知道小A的一切选择)

该题目可转换为小A选出\(m+1\)个数,而小B则要求出其最小收益,为

\[\min_{j=1}^{m+1}({B_j - \Sigma_{k=1}^j A_k}) \]

而对于这个式子,我们可以发现,对于\(B\)升序排序一定更优

而这个式子,很像在让我们做最小值最大,那么考虑二分答案

如果只是单纯的使用这个式子,时间复杂度为\(O(nmlogV)\),考虑优化

我们可以采取反悔贪心,使用优先队列,维护我们选的数

具体的,我们假设当前处理到\(i\),目前选择数的总价值为\(sum\),那么,这个数的贡献\(w\)就是\(B_i - A_i - sum\)

\(w \ge mid\),那么考虑直接插入队列中

否则,则可以跟队列中花费最大的值(j)进行比较,若\(A_i < A_j\),则可以考虑直接替换到这个点,这肯定是不劣的

最终,如果队列的长度\(\ge m+1\),则代表当前的\(mid\)是可行的,否则就不可行

示例代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define pii pair<ll,ll>
#define fi first 
#define se second
#define fstios ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
const int N = 1e5 + 10;
int n,m;
pii x[N];
bool cmp(pii a,pii b){
	return a.se < b.se;
}
bool check(ll mid){
	priority_queue< ll > q;
	ll sum = 0;
	for(int i=1;i<=n;i++){
		if(x[i].se - x[i].fi - sum >= mid){
			sum += x[i].fi;
			q.push(x[i].fi);
		}
		else{
			if(!q.empty() && x[i].fi < q.top()){
				sum += x[i].fi;
				sum -= q.top();
				q.pop();
				q.push(x[i].fi);
			}
		}
	}
	if(q.size() < m+1) return 0;
	return 1;
}
void solve(){
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> x[i].fi >> x[i].se;
	sort(x+1,x+1+n,cmp);
	ll l = 0,r = 1e18;
	ll ans = 0;
	while(l <= r){
		ll mid = (l + r) >> 1;
		if(check(mid)){
			l = mid + 1;
			ans = mid;
		}
		else r = mid - 1;
	}
	cout << ans << '\n';
	return ;
}
int main()
{
	fstios;
	File("shop");
	int T;
	cin >> T;
	while(T -- )solve();
	return 0;
}
posted @ 2025-07-21 19:16  WinterXorSnow  阅读(31)  评论(0)    收藏  举报