A/D-区间选点
题目描述
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点
使用差分约束系统的解法解决这道题
输入输出格式及样例
Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 1 <= ci <= bi - ai+1。
Output
输出一个整数表示最少选取的点的个数
Input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Output
6
思路
区间选点的问题上次使用的是贪心算法,这次使用差分。
将区间的两个端点抽象为图中两个点,对于区间[a,b],要满足该区间中至少有c个点,按照差分的思想就是,dis[b+1]-dis[a]>=c,对于其他点而要保证dis有意义,要满足0<=dis[i+1]-dis[i]<=1,因为相邻两个点之间的区间最多只有一个点,所以只能是0和1.
由于本题是求最小解,对建好的图求最长路即可得到答案。
实验代码
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 50010;
const int inf = 1000000;
int inq[maxn], dis[maxn], n, Max, Min;
struct Edge {//边
int v;
int w;
int nxt;
}edge[maxn];
int head[maxn], tot;
void init() {
memset(head, -1, sizeof(head));
tot = 0;
}
void add(int u, int v, int w) {
edge[++tot].v = v;
edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot;
}
void spfa(int s) {
queue<int> q;
for (int i = 1; i < maxn; i++) {
inq[i] = 0;
dis[i] = -inf;
}
dis[s] = 0;
inq[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = 0;
for (int i = head[u]; i != -1; i = edge[i].nxt) {
int v = edge[i].v;
int w = edge[i].w;
if (dis[v] < dis[u] + w) {
dis[v] = dis[u] + w;
if (!inq[v]) {
q.push(v);
inq[v] = 1;
}
}
}
}
}
int main(){
init();
scanf("%d", &n);
Max = 0;
Min = inf;
int a, b, c;
for (int i = 0; i < n; i++){
scanf("%d%d%d", &a, &b, &c);
Max = max(Max, b);
Min = min(Min, a);
add(a, b + 1, c);
}
for (int i = Min; i <= Max; i++){
add(i, i + 1, 0);
add(i + 1, i, -1);//补全相邻两个点的权值
}
spfa(Min);
cout << dis[Max + 1] << endl;
return 0;
}
B-猫猫向前冲
题目描述
众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。
输入输出格式及样例
Input
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示猫猫的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即编号为 P1 的猫猫赢了编号为 P2 的猫猫。
Output
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
Input
4 3
1 2
2 3
4 3
Output
1 2 4 3
思路
不同于上次的floyd算法求胜负关系,本题要求给出排名顺序,对于同级中没有比过的猫按编号小的靠前,也就是求字典序最小的排名序列,对于求排名,肯定是比赛赢的猫排名靠前,对于图来说,就是入度数为零的点排名靠前,所以很明显是使用拓扑排序的算法,将入度数为零的点加入队列,然后不断出队列,遍历队首元素的邻接点,使邻接点度数减一,如果减完度数等于零则加入队列,由于字典序要求最小,所以我们用优先级队列替换队列。
本题最开始出现错误,因为优先级队列没有使用cmp导致使用的是大根堆,而且初始化dis数组有问题,更改了之后就过了。
实验代码
#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 1e3 + 5;
int cnt[maxn], dis[maxn], n, m;
struct Edge {
int v;
//int w;
int nxt;
}edge[maxn];
int head[maxn], tot;
void init() {
memset(head, -1, sizeof(head));
tot = 0;
}
void add(int u, int v) {
edge[++tot].v = v;
//edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot;
}
void toposort(){
priority_queue<int,vector<int>, greater<int> > q;
vector<int> ans;
for (int i = 1; i <= n; i++)
if (cnt[i] == 0)
q.push(i);
while (!q.empty() ){
int u = q.top(); q.pop();
ans.push_back(u);
for (int i = head[u]; i != -1; i = edge[i].nxt){
int v = edge[i].v;
cnt[v]--;
if (cnt[v] == 0)
q.push(v);
}
}
if (ans.size() == n){
cout << ans[0];
for (int i = 1; i < n; i++)
cout << " " << ans[i];
cout << endl;
}
}
int main(){
while (cin >> n >> m){
init();
memset(cnt, 0, sizeof(cnt));
int p1, p2;
for (int i = 0; i < m; i++){
cin >> p1 >> p2;
add(p1, p2);
cnt[p2]++;
}
toposort();
}
return 0;
}
C-班长竞选
题目描述
大学班级选班长,N 个同学均可以发表意见
若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适
勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?
输入输出格式及样例
Input
本题有多组数据。第一行 T 表示数据组数。每组数据开始有两个整数 N 和 M (2 <= n <= 5000, 0 <m <= 30000),接下来有 M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
Output
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。
接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
Input
2
4 3
3 2
2 0
2 13 3
1 0
2 1
0 2
Output
Case 1: 2
0 1
Case 2: 2
0 1 2
思路
由于选举也具有传递性,所以本题最开始想的是floyd算法,但是floyd算法复杂度太高,对于本题时间不允许。然后是对于环路的处理,环路里的所有点的票数都至少有该环路的点数减一,环路与环路之间的票数可以单独处理,所以本题将环路优化为一个点,求出优化后的点的票数,最后加上环路自身的票数就可。
对于求环的方法,我们采用的是有向图kosaraju算法求强连通分量,kosaraju算法的思想是,先dfs求出后序序列,然后按照逆后序序列的顺序对反图再进行一次dfs就可以求出强连通分量,求出强连通分量后,我们将强连通分量优化为一个点,对优化后的图求反,对反图中入度数为0的点跑一次dfs,计算他的票数,最后求出票数的最大值。
实验代码
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 30010;
struct Edge {//边
int v;
//int w;
int nxt;
}edge1[maxn], edge2[maxn];
int head1[maxn], tot1;//原图链式前向星
void init1() {
memset(head1, -1, sizeof(head1));
tot1 = 0;
}
void add1(int u, int v) {
edge1[++tot1].v = v;
//edge[tot].w = w;
edge1[tot1].nxt = head1[u];
head1[u] = tot1;
}
int head2[maxn], tot2;//反图链式前向星
void init2() {
memset(head2, -1, sizeof(head2));
tot2 = 0;
}
void add2(int u, int v) {
edge2[++tot2].v = v;
//edge[tot].w = w;
edge2[tot2].nxt = head2[u];
head2[u] = tot2;
}
int n, m, dcnt, scnt, vis[maxn], dfn[maxn], c[maxn], sum[maxn], ans[maxn], degree[maxn];
void dfs1(int x) {
vis[x] = 1;
for (int i = head1[x]; i != -1; i = edge1[i].nxt) {
int v = edge1[i].v;
if (!vis[v])
dfs1(v);
}
dfn[++dcnt] = x;
}
void dfs2(int x) {
c[x] = scnt;
sum[scnt]++;
for (int i = head2[x]; i != -1; i = edge2[i].nxt) {
int v = edge2[i].v;
if (!c[v])
dfs2(v);
}
}
void kosaraju() {
dcnt = scnt = 0;
memset(c, 0, sizeof(c));
memset(vis, 0, sizeof(vis));
memset(sum, 0, sizeof(sum));
for (int i = 0; i < n; i++)
if (!vis[i]) dfs1(i);
for (int i = n; i >= 1; i--)
if (!c[dfn[i]]) {
++scnt;
dfs2(dfn[i]);
}
}
void dfs(int x, int u) {
vis[x] = 1;
for (int i = head2[x]; i != -1; i = edge2[i].nxt) {
int v = edge2[i].v;
if (!vis[v]) {
vis[v] = 1;
ans[u] += sum[v];
dfs(v, u);
}
}
}
int main(){
int T;
cin >> T;
int cnt = 0;
while (T--){
cnt++;
init1(); init2();
memset(degree, 0, sizeof(degree));
memset(ans, 0, sizeof(ans));
cin >> n >> m;
int a, b;
for (int i = 0; i < m; i++){
scanf("%d%d", &a, &b);
add1(a, b);
add2(b, a);
}
kosaraju();
init2();
for (int i = 0; i < n; i++) {
for (int j = head1[i]; j != -1; j = edge1[j].nxt) {//将强连通分支化简为一个点
int v = edge1[j].v;
//cout << c[i] << " " << c[v] << endl;
if (c[i] != c[v]) {
degree[c[i]]++;
add2(c[v], c[i]);//反图
}
}
}
int Max = 0;
//在反图中,求每个强连通分支所能到达的强连通分支的点数和,加上自己强连通分支的点数再减一,就是他的票数
for (int i = 1; i <= scnt; i++) {
if (degree[i] == 0) {
memset(vis, 0, sizeof(vis));//初始化vis数组避免dfs出错
dfs(i, i);
ans[i] += sum[i] - 1;
//cout << ans[i] << endl;
}
if (ans[i] > Max)
Max = ans[i];
}
cout << "Case " << cnt << ": " << Max << endl;
vector<int> q;
for (int i = 0; i < n; i++) {
if (ans[c[i]] == Max)
q.push_back(i);
}
n = q.size();
//cout << n << endl;
cout << q[0];
for (int i = 1; i < n; i++)
cout << " " << q[i];//输出答案
cout << endl;
}
return 0;
}