A - 区间选点 II
题意:
给定一个数轴上的 n n n 个区间,要求在数轴上选取最少的点使得第 i i i 个区间 [ a i , b i ] [ai, bi] [ai,bi] 里至少有 c i ci ci 个点
使用差分约束系统的解法解决这道题
Input
输入第一行一个整数 n n n 表示区间的个数,接下来的 n n n 行,每一行两个用空格隔开的整数 a , b a,b a,b 表示区间的左右端点。 1 < = n < = 50000 , 0 < = a i < = b i < = 50000 1 <= n <= 50000, 0 <= ai <= bi <= 50000 1<=n<=50000,0<=ai<=bi<=50000 并且 1 < = c i < = b i − a i + 1 1 <= ci <= bi - ai+1 1<=ci<=bi−ai+1。
Output
输出一个整数表示最少选取的点的个数
Sample Input
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
Sample Output
6
思路做法:
要求不能使用贪心法,需要用差分约束的方法将原题转化为图的最短路或最长路问题。
令sum[i]代表[0, i)区间内的点的个数(点都是整数值),则sum[b+1]-sum[a]表示[a, b]内点的个数。
原限制就可以表示为
s
u
m
[
b
+
1
]
−
s
u
m
[
a
]
>
=
c
sum[b+1]-sum[a]>=c
sum[b+1]−sum[a]>=c,因为点的个数要尽可能的少,所以式子变化为
s
u
m
[
b
+
1
]
>
=
s
u
m
[
a
]
+
c
sum[b+1]>=sum[a]+c
sum[b+1]>=sum[a]+c,其中
s
u
m
[
b
+
1
]
sum[b+1]
sum[b+1]取最小值即为
s
u
m
[
a
]
+
c
sum[a]+c
sum[a]+c,若
b
b
b是总区间最大值,则
s
u
m
[
b
]
sum[b]
sum[b]就是答案。
除了以上一系列输入的限制外,系统自带的限制有
0
<
=
s
u
m
[
i
]
−
s
u
m
[
i
−
1
]
<
=
1
0<=sum[i]-sum[i-1]<=1
0<=sum[i]−sum[i−1]<=1,转化为
s
u
m
[
i
]
>
=
s
u
m
[
i
−
1
]
+
0
,
s
u
m
[
i
−
1
]
>
=
s
u
m
[
i
]
+
(
−
1
)
sum[i]>=sum[i-1]+0, sum[i-1]>=sum[i]+(-1)
sum[i]>=sum[i−1]+0,sum[i−1]>=sum[i]+(−1),将其作为边放入图中,并从0号结点运行SPFA(判断负环)计算最长路
总结:
当跑最短路或最长路时,要注意inf值得选取,不是任一个很大的值都可以。
另外当[a, b]区间内点的个数用sum[b]-sum[a-1](其中sum[i]表示[0, i]区间内点的个数)表示时,碰到a=0的情况难以处理,因为SPFA算法是从0开始的,所以要改变一下形式。
代码:
#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;
const int N = 5e4+50;
const int inf = 1e8; // 0x3f3f3f3f
int sum[N]; // b <= 50000 [0,i)区间内的点数 点为整数
struct Edge{
int v, w;
Edge(){}
Edge(int _v, int _w):v(_v), w(_w){}
};
vector<Edge> E[N];
queue<int> inq;
int dis[N], vis[N], n;
void addEdge(int a, int b, int c){
E[a].push_back(Edge(b, c));
}
void spfa(int s){
for(int i = 0; i <= n; ++i){
dis[i] = -inf; vis[i] = 0; // 初始化
}
inq.push(s); dis[s] = 0; vis[s] = 1;
while(!inq.empty()){
int u = inq.front(); inq.pop(); vis[u] = 0;
for(int i = 0, len = E[u].size(); i < len; ++i){
int v = E[u][i].v, w = E[u][i].w;
if(dis[v] < dis[u] + w){ // 最长路
dis[v] = dis[u] + w;
if(!vis[v]){
inq.push(v); vis[v] = 1; // 防止重复
}
}
}
}
}
int main(){
int m, max_b = 0; scanf("%d", &m); // m个区间
for(int i = 1; i <= m; ++i){
int a, b, c; //[a,b+1)=[a,b]里至少有c个点
scanf("%d%d%d", &a, &b, &c);
if(b+1 > max_b) max_b = b+1; //更新 max_b 最大点
addEdge(a, b+1, c); // 防止-1出现
}
n = max_b;
for(int i = 1; i <= n; ++i){
addEdge(i, i-1, -1); addEdge(i-1, i, 0);
}
spfa(0);
// for(int i = 0; i <= n; ++i){
// for(int j = 0; j < E[i].size(); ++j)
// printf("%d %d %d ", i, E[i][j].v, E[i][j].w);
// printf("\n");
// }
printf("%d\n", dis[max_b]);
return 0;
}
B - 猫猫向前冲
题意:
众所周知, TT 是一位重度爱猫人士,他有一只神奇的魔法猫。
有一天,TT 在 B 站上观看猫猫的比赛。一共有 N 只猫猫,编号依次为1,2,3,…,N进行比赛。比赛结束后,Up 主会为所有的猫猫从前到后依次排名并发放爱吃的小鱼干。不幸的是,此时 TT 的电子设备遭到了宇宙射线的降智打击,一下子都连不上网了,自然也看不到最后的颁奖典礼。
不幸中的万幸,TT 的魔法猫将每场比赛的结果都记录了下来,现在他想编程序确定字典序最小的名次序列,请你帮帮他。
Input
输入有若干组,每组中的第一行为二个数 N ( 1 < = N < = 500 ) , M N(1<=N<=500),M N(1<=N<=500),M;其中 N N N表示猫猫的个数, M M M表示接着有 M M M行的输入数据。接下来的 M M M行数据中,每行也有两个整数 P 1 , P 2 P_1,P_2 P1,P2表示即编号为 P 1 P_1 P1 的猫猫赢了编号为 P 2 P_2 P2 的猫猫。
Output
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名
Sample Input
4 3
1 2
2 3
4 3
Sample Output
1 2 4 3
思路做法:
将边用vector存入后跑拓扑排序算法,值得注意的是题目要求输出要编号小的队伍在前,因此用优先级队列,priority_queue为大根堆,因此为了先拿出编号小的队伍,把队伍编号取负号放入队列中,拿出来后再正回来放入答案数组中。
总结:
很基础的拓扑排序算法问题,并且没有任何变化,直接跑代码即可,注意多组输入要初始化。
代码:
#include <stdio.h>
#include <vector>
#include <queue>
using namespace std;
const int N = 5e2+5;
vector<int> E[N];
int n, in[N], ans[N], cnt;
priority_queue<int> q; // 大根堆
void toposort(){
while(!q.empty()) q.pop(); // 初始化
for(int i = 1; i <= n; ++i){
if(in[i] == 0) q.push(-i); // 取负号 变小根堆
}
cnt = 0;
while(!q.empty()){
int u = -q.top(); q.pop();
ans[cnt++] = u; // 放到结果数组中
for(int i = 0, len = E[u].size(); i < len; ++i){
int v = E[u][i]; --in[v];
if(in[v] == 0) q.push(-v);
}
}
}
int main(){
int m;
while(~scanf("%d%d", &n, &m)){
for(int i = 1; i <= n; ++i){
in[i] = 0; E[i].clear(); // 初始化
}
for(int i = 1; i <= m; ++i){
int p1, p2; scanf("%d%d", &p1, &p2);
E[p1].push_back(p2);
in[p2]++; // 入度加
}
toposort();
if(cnt == n){
for(int i = 0; i < cnt-1; ++i)
printf("%d ", ans[i]);
printf("%d\n", ans[cnt-1]);
}
}
return 0;
}
C - 班长竞选
题意:
大学班级选班长,N 个同学均可以发表意见 若意见为 A B 则表示 A 认为 B 合适,意见具有传递性,即 A 认为 B 合适,B 认为 C 合适,则 A 也认为 C 合适 勤劳的 TT 收集了M条意见,想要知道最高票数,并给出一份候选人名单,即所有得票最多的同学,你能帮帮他吗?
Input
本题有多组数据。第一行 T T T 表示数据组数。每组数据开始有两个整数 N N N 和 M ( 2 < = n < = 5000 , 0 < m < = 30000 ) M (2 <= n <= 5000, 0 <m <= 30000) M(2<=n<=5000,0<m<=30000),接下来有 M M M 行包含两个整数 A 和 B(A != B) 表示 A 认为 B 合适。
Output
对于每组数据,第一行输出 “Case x: ”,x 表示数据的编号,从1开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
Sample Input
2
4 3
3 2
2 0
2 1
3 3
1 0
2 1
0 2
Sample Output
Case 1: 2
0 1
Case 2: 2
0 1 2
思路做法:
首先在存储的图上先跑一遍dfs(dfs1函数)记录后序计数(用vis1数组记录是否访问过),根据反过来的后序计数(即逆后序)再遍历一遍(dfs2函数)整个图记录下强连通分量的个数(scnt)及每个强连通分量内部节点数(SCC[scnt])由于每个强连通分量内的节点的投票数相同,在反图上将其缩点,并且在缩点后的图中找入度为0的点放入数组中(因为反图的每条边都与原来的边方向相反,因此入度为0说明在原图中出度为0,容易想到得票数最多的一定是出度为0的点),对数组中每个点执行一遍便历(dfs3)计算总投票数(用vis2记录是否访问过),找出最大的来,但注意最大的不一定只有1个,所以用vector记录下所有符合的点,输出总投票数减1(不能算自己投自己的票),并便历所有原图中的点,判断其是否在结果强连通分量中,最后注意输出的格式。
总结:
多组输入必须考虑各个变量的初始化,尤其是代码中需要的变量很多,但是大部分都要先清空再使用。
代码:
#include <stdio.h>
#include <vector>
using namespace std;
const int N = 5e3 + 50;
vector<int> E1[N], E2[N], E3[N], cc; // E2反图 E3缩点后的图
int n, vis[N], c[N], dfn[N], SCC[N];
int dcnt, scnt, in[N], vis2[N], sum;
void dfs1(int u){
vis[u] = 1;
for(int i = 0, len = E1[u].size(); i < len; ++i){
int v = E1[u][i];
if(!vis[v]) dfs1(v);
}
dfn[++dcnt] = u; // 遍历完 记录下来 后序计数
}
void dfs2(int u){
c[u] = scnt; SCC[scnt]++; // 强连通分量内点数
for(int i = 0, len = E2[u].size(); i < len; ++i){
int v = E2[u][i]; // 反图
if(c[v] == 0) dfs2(v);
}
}
void dfs3(int u){
vis2[u] = 1; sum += SCC[u];
for(int i = 0, len = E3[u].size(); i < len; ++i){
int v = E3[u][i];
if(!vis2[v]) dfs3(v);
}
}
void Kosaraju(){
dcnt = 0; scnt = 0; // dfs序计数,scc计数
for(int i = 0; i < n; ++i){
c[i] = 0; vis[i] = 0; // 初始化 c: scc编号
}
for(int i = 1; i <= n; ++i) SCC[i] = 0;
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]);
}
}
}
bool incc(int u){
for(int i = 0, len = cc.size(); i < len; ++i){
if(c[u] == cc[i]) return true;
}
return false;
}
int main(){
int T; scanf("%d", &T);
for(int t = 1; t <= T; ++t){
int m; scanf("%d%d", &n, &m);
for(int i = 0; i < n; ++i){ // 编号 0 ~ n-1
E1[i].clear(); E2[i].clear(); E3[i].clear();
}
for(int i = 1; i <= m; ++i){
int a, b; scanf("%d%d", &a, &b);
E1[a].push_back(b); E2[b].push_back(a);
}
Kosaraju(); // 确定强连通分量
for(int i = 1; i <= scnt; ++i) in[i] = 0; // 初始化
for(int i = 0; i < n; ++i){ // 缩点
for(int j = 0, len = E2[i].size(); j < len; ++j){
if(c[i] == c[E2[i][j]]) continue;
E3[c[i]].push_back(c[E2[i][j]]);
in[c[E2[i][j]]]++; // 入度加
}
}
int ans = 0, num = 0, temp[N];
for(int i = 1; i <= scnt; ++i){
if(in[i] == 0) temp[num++] = i; // 入度为0的强连通分量
}
cc.clear();
for(int i = 0; i < num; ++i){
for(int j = 1; j <= scnt; ++j) vis2[j] = 0;
sum = 0; dfs3(temp[i]);
if(sum > ans){
ans = sum; cc.clear(); cc.push_back(temp[i]);
}else if(sum == ans) cc.push_back(temp[i]);
}
printf("Case %d: %d\n", t, ans-1);
bool occur = 0;
for(int i = 0; i < n; ++i){
if(incc(i)){
if(!occur){
printf("%d", i); occur = 1;
}else printf(" %d", i);
}
}
printf("\n");
}
return 0;
}