20240309

瑞士轮

思路:

快排会g,所以要归并排序

define int long long会g,关掉

快排函数: stable_sort,用法和sort一样

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

// #define int long long
struct inf {
    int score;
    int id;
    int force;
};

bool cmp(inf a, inf b) {
    if(a.score == b.score) {
        return a.id < b.id;
    }
    return a.score > b.score;
}

void solve() {
    int N, R, Q;
    cin >> N >> R >> Q;

    N *= 2;
    vector<inf> v(N);
    for (int i = 0; i < N; i++) {
        v[i].id = i + 1;
        cin >> v[i].score;
    }
    for (int i = 0; i < N; i++) {
        cin >> v[i].force;
    }

    
    stable_sort(v.begin(), v.end(), cmp);
    
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < N; j += 2) {
            if(v[j].force > v[j + 1].force) {
                v[j].score++;
            }
            else {
                v[j + 1].score++;
            }
        }
        stable_sort(v.begin(), v.end(), cmp);
    }
    
    cout << v[Q - 1].id << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

其实这是靠归排卡过去的,正解如下:

在第一次排完序之后的第一场比赛结束后,赢者的顺序和败者的顺序相对不变,就是两个集合,因为赢都是加一分,所以我们考虑把两个顺序不变的数组合并起来:归并!

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

// #define int long long
struct inf {
    int score;
    int id;
    int force;
};

bool cmp(inf a, inf b) {
    if(a.score == b.score) {
        return a.id < b.id;
    }
    return a.score > b.score;
}

void solve() {
    int N, R, Q;
    cin >> N >> R >> Q;

    N *= 2;
    vector<inf> v(N);
    for (int i = 0; i < N; i++) {
        v[i].id = i + 1;
        cin >> v[i].score;
    }
    for (int i = 0; i < N; i++) {
        cin >> v[i].force;
    }

    
    stable_sort(v.begin(), v.end(), cmp);
    
    for (int i = 0; i < R; i++) {
        for (int j = 0; j < N; j += 2) {
            if(v[j].force > v[j + 1].force) {
                v[j].score++;
            }
            else {
                v[j + 1].score++;
            }
        }
        stable_sort(v.begin(), v.end(), cmp);
    }
    
    cout << v[Q - 1].id << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

B - 传送门

思路:

首先开两个边值数组

四重循环,枚举边权为0的边需要两重循环[i, j]

然后再枚举两个点[k, g]看是否更新边权,看[k, i] + [j, g] 和[k, j] + [i, g],这两条边是否能更新[k,g]

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

#define int long long
int e[105][105];
int ee[105][105];

void solve() {
    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if(i != j) {
                e[i][j] = 1e18;
            }
            else {
                e[i][j] = 0;
            }
        }
    }

    for (int i = 0; i < m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u][v] = e[v][u] = w;
    }

    for (int k = 1; k <= n; k++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if(e[i][k] != 1e18 && e[k][j] != 1e18) {
                    e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
                }            
            }
        }
    }

    int ans = 1e18;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {

            for (int k = 1; k <= n; k++) {
                for (int g = 1; g <= n; g++) {
                    ee[k][g] = e[k][g];
                }
            }
            ee[i][j] = ee[j][i] = 0;

            for (int k = 1; k <= n; k++) {
                for (int g = 1; g <= n; g++) {
                    ee[k][g] = min(ee[k][g], min(ee[k][i] + ee[j][g], ee[k][j] + ee[i][g]));
                }
            }
            int res = 0;
            for (int k = 1; k <= n; k++) {
                for (int g = k + 1; g <= n; g++) {
                    res += ee[k][g];
                }
            }
            ans = min(ans, res);
        }
    }

    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;

    while(T--) {
        solve();
    }

    return 0;
}

D - 菜肴制作

思路:

因为题目有边的限制条件(正常拓扑)然后要输出序号的顺序尽可能小的先输出

为什么要反向拓扑?

我理解的是,正向拓扑优先队列小的先输出不行,因为题目要求优先输出小的,但是小的可能藏在大的后面,所以正向不能贪心

但是反向贪心是正确的:因为我们保证反向先输出大的,而反向的起点正是正向的终点,也就是输出的结果,所以反向拓扑可以贪心保证正确性

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

// #define int long long
vector<int> adj[100005];

void solve() {
    int n, m;
    cin >> n >> m;

    vector<int> in(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        adj[i].clear();
    }

    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        adj[v].push_back(u);
        in[u]++;
    }

    priority_queue<int> q;
    for (int i = 1; i <= n; i++) {
        if(in[i] == 0) {
            q.push(i);
        }
    }

    vector <int> ans;
    while(q.size()) {
        int x = q.top();
        ans.push_back(x);
        q.pop();

        for (auto it : adj[x]) {
            if(--in[it] == 0) {
                q.push(it);
            }
        }
    }

    if(ans.size() == n) {
        for (int i = n - 1; i >= 0; i--) {
            cout << ans[i] << " ";
        }
        cout << endl;
    }
    else {
        cout << "Impossible!\n";
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    cin >> T;

    while(T--) {
        solve();
    }

    return 0;
}

E - 数据备份

题意:

n个点,n个点距离起点的距离已知,要求连k条边,2k个边的顶点相异,求这k跳变的最小长度和

思路:

题目范围是 $N[1, 10 ^ 5]$ 时间是500ms,要求算法是O(nlogn)级别的

这个题是个反悔贪心,核心机制是选一个点,然后这个点的权值设置成 (l + r) - x,如果再选这个就相当于选了两边的然后扔掉中间的,妙

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

#define int long long

struct node {
    int l, r;
    int id;
    int val;
};

struct inf {
    int val;
    int id;

    bool operator< (const inf & a) const {
        return val > a.val;
    }
};

void solve() {
    int n, m;
    cin >> n >> m;

    vector<int> vv(n);
    for (int i = 0; i < n; i++) {
        cin >> vv[i];
    }

    vector<int> v;
    v.push_back(0);

    for (int i = 1; i < n; i++) {
        v.push_back(vv[i] - vv[i - 1]);
    }

    priority_queue<inf> q;
    for (int i = 1; i < n; i++) {
        q.push({v[i], i});
    }
    // 1 - 4

    vector<int> vis(n + 1, 0);
    vector<node> lst(n + 1);

    lst[0].val = lst[n].val = (int)(9e18);
    for (int i = 1; i < n ; i++) {
        lst[i].l = i - 1;
        lst[i].r = i + 1;
        
        lst[i].id = i;
        lst[i].val = v[i];
    }   

    int ans = 0;
    int cnt = 0;
    while(cnt < m) {
        auto it = q.top();
        q.pop();
        // cerr << it.id << " " << it.val << endl;
        if(vis[it.id] == 1) {
            continue;
        }
        ans += it.val;
        int _l = lst[it.id].l;
        int _r = lst[it.id].r;
        vis[_l] = 1, vis[_r] = 1;

        lst[it.id].val = lst[_l].val + lst[_r].val - lst[it.id].val;
        q.push({lst[it.id].val, it.id});

        lst[it.id].l = lst[_l].l;
        lst[it.id].r = lst[_r].r;
        lst[lst[_l].l].r = it.id;
        lst[lst[_r].r].l = it.id;

        cnt++;
    }
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int T = 1;
    // cin >> T;

    while(T--) {
        solve();
    }

    return 0;
}

反悔贪心

  • 前两道题都是利用堆的特性,维护一个利益最大值,每次更新失败的时候:把元素压入堆,把堆顶元素去除。

  • 反悔贪心允许我们在发现现在不是全局最优解的情况下,回退一步或者若干步,去采取另外的策略得到全局最优解。

P2949 [USACO09OPEN] Work Scheduling G

题意:

n件事,每件事有截止日期和收益,有1e9的时间(无限长),但是每一秒只能做一件事,一件事只需要一秒。

问能获得的最大收益?

思路:

起初就是按照时间第一关键字,收益第二关键字排序,然后只要时间够就加,贪心只有40分

我们思考一下,如果在后期有收益比前期高的事件,可以放弃做前面那个,优先做后面那个

实现:按第一关键字排序之后,小根堆维护事件收益。

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

#define int long long

void solve() {
	int n;
	cin >> n;

	vector<pair<int, int>> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].first >> v[i].second;
	}

	sort(v.begin(), v.end());

	priority_queue<int, vector<int>, greater<int>>q;

	int day = 0;
	int res = 0;
	for (auto [t, w] : v) {
		if(day + 1 <= t) {
			res += w;
			q.push(w);
			day++;
		}
		else {
			if(q.top() < w) {
				res -= q.top();
				q.pop();
				q.push(w);
				res += w;
			}
		}
	}

	cout << res << endl;
 
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}

P4053 [JSOI2007] 建筑抢修

题意:

n个建筑需要修复,对于每个建筑,给两个参数$t1, t2$,表示修筑需要的时间和修筑的截止时间。

问能最多能修复多少建筑。

思路:

这个题的特点就是选择一种策略,来找最多的东西,看着像背包dp,其实是个反悔贪心。

先搞清楚怎么才能让建筑尽可能多?那就是建筑所需要的时间尽可能少。

利用大根堆维护修复建筑所需要的时间。

我们先根据t2排序,然后循环遍历

  • 如果当前的时间足以修复这个建筑,那就修
  • 否则,就说明之前的时间已经过了修复这个建筑的时间,我们要把这个建筑压入大根堆,然后把最大的建筑时间剔除,这就是反悔
#include <bits/stdc++.h>
using namespace std;

#define int long long

struct  inf{
	int t1, t2;
};

bool cmp (inf a, inf b) {
	return a.t2 < b.t2;
}

void solve() {
	int n;
	cin >> n;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].t1 >> v[i].t2;
	}

	sort(v.begin(), v.end(), cmp);

	priority_queue<int> q;
	int sum = 0;
	int ans = 0;
	for (auto [t1, t2] : v) {
		q.push(t1);
		sum += t1;
		if(sum <= t2) {
			ans++;
		}
		else {
			sum -= q.top();
			q.pop();
		}
	}

	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
    
	while(T--) {
		solve();
	}

	return 0;
}

P2107 小Z的AK计划

题意:

n个点,m的时间。

每个点有位置和停留时间,问最多解决的点数。

思路:

这个题我学会了,反悔贪心就是先做,后反悔,

就像这个题,我们直接按照位置顺序排序,

然后对时间用大根堆维护,每次取优。

直到time < 0 我们开始出队,如果出到time < 0,就break

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

#define int long long

struct inf {
	int x, t;
};

bool cmp (inf a, inf b) {
	return a.x < b.x;
}

void solve() {
	int n, m;
	cin >> n >> m;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].x >> v[i].t;
	}

	int time = 10;
	int loc = 0;
	int ans = 0;
	int res = 0;
	sort(v.begin(), v.end(), cmp);

	priority_queue<int> q;
	for (auto [x, t] : v) {
		if(time - x + loc - t >= 0) {
			time -= t + x - loc;
			q.push(t);
			ans++;
			loc = x;
		}
		else {
			while(q.size()) {
				time += q.top();
				q.pop();
				ans--;
				if(time - x + loc - t >= 0) {
					break;
				}
			}
			if(time - x + loc - t >= 0) {
				q.push(t);
				time -= x + t;
			}
			else 
		}
		res = max(res, ans);
	}

 	cout << res << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}

Buy Low Sell High

题意:

n天,

上午进$a_i$件,下午有一个人买$b_i$ 件,问最多能满足多少人的需求

思路:

贪心直接做会出现大客户的情况,所以要反悔,能卖就卖,开一个大根堆,维护大客户

最后输出

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

#define int long long

struct inf {
	int a, b;
};

struct inf2 {
	int t;
	int id;

	bool operator<(const inf2& a) const{
		return t < a.t;
	}
};

void solve() {
	int n;
	cin >> n;

	vector<inf> v(n);
	for (int i = 0; i < n; i++) {
		cin >> v[i].a;
	}
	for (int i = 0; i < n; i++) {
		cin >> v[i].b;
	}

	int cnt = 0;
	int ans = 0;

	priority_queue<inf2> q;
	for (int i = 0; i < n; i++) {

		int a = v[i].a, b = v[i].b;
		cnt += a;

		q.push({b, i + 1});
		cnt -= b;
		if(cnt >= 0) {
			ans++;
		}
		else {
			cnt += q.top().t;
			q.pop();
		}
	}

	cout << ans << endl;
	vector<int> vt;

	while(q.size()) {
		vt.push_back(q.top().id);
		q.pop();
	}

	sort(vt.begin(), vt.end());
	for (auto it : vt) {
		cout << it << " ";
	}
	cout << endl;
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);

	int T = 1;
	// cin >> T;

	while(T--) {
		solve();
	}

	return 0;
}
posted @ 2024-03-09 22:54  contiguous  阅读(13)  评论(0编辑  收藏  举报