8.13(优先队列贪心维护+打表找规律+对顶堆优先队列+DFS减枝+贪心dp)
nowcoder
第k小
我没有搞清楚题目的意思。以为要全部保留所有的数,其实不然
题目只需要保留到第k位即可,我们可以开一个优先队列去存值
查询小于k输出-1,大于k,pop掉
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m,k;
cin>>n>>m>>k;
priority_queue<int,vector<int>,less<int>>q;
for (int i = 0; i < n; i++) {
int a;
cin>>a;
q.push(a);
}
while(q.size()>k)q.pop();
while(m--){
int b;
cin>>b;
if(b!=1){
if(k>q.size())cout<<-1<<'\n';
else
cout<<q.top()<<'\n';
}else{
int c;
cin>>c;
q.push(c);
if(q.size()>k)q.pop();
}
}
return 0;
}
Running Median
使用对顶堆可解,左边的堆维护中位数前半部分,右边的堆维护中位数后半部分。在新的数进来的时候判断应该插左边还是右边,然后对左右如果差值超过1进行调整。
这样要么左堆的顶端是中位数,要么当左右相同大小的时候就是两个堆顶的数取平均数。
没有怎么练过这种题,对顶堆去维护某个值
#include <bits/stdc++.h>
using namespace std;
int p;
int main() {
cin>>p;
while (p--) {
priority_queue<int, vector<int>, less<int> > zuo;
priority_queue<int, vector<int>, greater<int> > you;
int num, m, val;
cin>>num>>m;
vector<int> v;
for (int i=1;i<=m;i++) {
cin>>val;
if (zuo.size()==you.size()) {
if (zuo.size()==0) {
zuo.push(val);
} else if (you.top()<val)
{
you.push(val);
zuo.push(you.top());
you.pop();
} else {
zuo.push(val);
}
}else {
if (zuo.top()>=val) {
zuo.push(val);
you.push(zuo.top());
zuo.pop();
} else {
you.push(val);
}
}
if (i&1==1) {
if (!zuo.empty()) {
if (zuo.size()==you.size()) v.push_back((zuo.top()+you.top())/2);
else v.push_back(zuo.top());
}
}
}
cout<<num<<" "<<v.size()<<endl;
int i;
for (i=0;i<v.size();i++) {
cout<<v[i]<<" ";
if ((i+1)%10==0) {
cout<<endl;
}
}
if (i%10!=0)
cout<<endl;
}
return 0;
}
tokitsukaze and Soldier
我想到怎么去分堆去堆放,但是没有想到怎么去维护每个s[i]的最大时攻击力最大,从大佬的
思路得知,我们从后往前插入优先队列,一直往前加,如果当前的人数大于对堆内最小的k,那么把堆内最小的攻击力推出去
这样就能维护每个s[i]的最大攻击力。
思路:我们考虑按s[i]存入vectoc,从大到小枚举s[i]的值。那么就是在所有>=s[i]的士兵中选v最大的s[i]个。我们可以优先队列维护。因为s[i]是减小的。所以删除的士兵一定在后面用不到。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
priority_queue<LL, vector<LL>, greater<LL> > q;
vector<LL> ve[100005];
int main(){
int n; scanf("%d", &n);
for(int i=1; i<=n; i++){
LL v, s;
scanf("%lld%lld", &v, &s);
ve[s].push_back(v);
}
LL ans=0, sum=0;
for(int i=n; i>=1; i--){
for(auto u: ve[i]){
sum+=u;
q.push(u);
}
while(q.size()>i){
sum-=q.top();
q.pop();
}
ans=max(ans, sum);
}
printf("%lld\n", ans);
return 0;
}
[JSOI2007]建筑抢修
贪心+优先队列,
贪心策略:
有三种
- 按需要时间短的来做
- 按最晚开始时间做(结束减去修时间)
3.按截止的时间早晚来做
第一种虽然时间短,但是截止时间晚,做了这件事,耽误你之前做的时间截止少的事
第二种开始时间早,但是修理时间长,那肯定也不行,修理时间完全可以做更多的事
第三种按照截止时间做,如果这件事做时间很长,但是后面已经可以做两件或者更多事
这个时候是有问题的。导致我们本来可以做后面两件事却被这件事耽误了
大佬的题解
#include <iostream>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//代码预处理区
const int MAX = 1e5 + 5e4 + 7;
struct Node {
int repair, time;
bool operator < (const Node& v) const {
if (time == v.time) return repair < v.repair;
return time < v.time;
}
} info[MAX];
priority_queue<int, vector<int>, less<int> > pq;
//全局变量区
int main() {
IOS;
int n; cin >> n;
for (int i = 1; i <= n; i++) cin >> info[i].repair >> info[i].time;
sort(info + 1, info + 1 + n);
int time = 0;
for (int i = 1; i <= n; i++) {
if (time > info[i].time) continue;//已经报废了
if (time + info[i].repair <= info[i].time) {
time += info[i].repair;
pq.push(info[i].repair);
}
//如果可以修就直接修
else {
if (pq.top() > info[i].repair) {
time -= pq.top() - info[i].repair;
pq.pop(); pq.push(info[i].repair);
}
}
//修不了就判断他是不是比已经修了的中最划不来的划得来,划得来就换这个
}
cout << pq.size() << endl;
return 0;
}
选择困难症
这道题就是一道简单的DFS,但是我不知道怎么剪枝老是出问题
正确的方法是我们每sum>k时,把后面的情况全部算进去,这样就剪枝了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=105;
int k,m;
int a[10];
int v[10][N];
ll ans=0;
void dfs(int u,int val)
{
if(u>k+1) return;
if(val>m)
{
ll temp=1;
for(int i=u;i<=k;i++)
{
temp*=(a[i]+1);
}
ans+=temp;
return;
}
for(int i=1;i<=a[u];i++)
{
dfs(u+1,val+v[u][i]);
}
dfs(u+1,val);
}
int main()
{
while(cin>>k>>m)
{
ans=0;
for(int i=1;i<=k;i++)
{
cin>>a[i];
for(int j=1;j<=a[i];j++)
{
scanf("%d",&v[i][j]);
}
sort(v[i]+1,v[i]+1+a[i]);
}
dfs(1,0);//层数 价值
cout<<ans<<endl;
}
return 0;
}
codeforces
C. Mad MAD Sum
这道题不是一点不会做,而是题目要搞清楚,要去手打几遍,要去打表找规律
当我们跑一次发现,数列已经是不下降的了,第二次跑完已经把所有数量为1的数全部删除了
最后剩下的都是大于等于2的数,我们把每个数加的次数累加上即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int sum=0;
int a;
cin>>a;
vector<int>v(a+1);
for (int i = 1; i <=a ; ++i) {
cin >> v[i];
}
for(int i=1;i<=2;i++){
int maxx=0;
unordered_map<int,int>mp;
for(int j=1;j<=a;j++){
sum+=v[j];
mp[v[j]]++;
if(mp[v[j]]>1)maxx=max(maxx,v[j]);
v[j]=maxx;
}
}
for(int i = 1 ; i <= a ; i ++) {
sum += v[i] * (a-i+1);
}
cout << sum << "\n";
}
return 0;
}
C. Basil's Garden
dp思维,从后面状态转移到前面,如果前面的数不大于后面的和,前面的数由后面转移加1,否则变成前面的数
#include <bits/stdc++.h>
using namespace std;
#define int long long
int f[1008611];
int32_t main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin>>n;
int maxx=0;
vector<int>v(n+1);
for (int i = 1; i <=n ; i++) {
cin>>v[i];
}
f[n]=v[n];
for(int j=n-1;j>=1;j--){
if(v[j]>f[j+1]){
f[j]=v[j];
}else{
f[j]=f[j+1]+1;
}
}
cout<<f[1]<<'\n';
}
return 0;
}