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则要求出其最小收益,为
而对于这个式子,我们可以发现,对于\(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;
}

浙公网安备 33010602011771号