A - 区间选点 II
给定一个数轴上的 n 个区间,要求在数轴上选取最少的点使得第 i 个区间 [ai, bi] 里至少有 ci 个点。
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
使用差分约束系统的解法解决这道题
Input
输入第一行一个整数 n 表示区间的个数,接下来的 n 行,每一行两个用空格隔开的整数 a,b 表示区间的左右端点。1 <= n <= 50000, 0 <= ai <= bi <= 50000 并且 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]\)之间选点的个数,那么对于第i个区间\([a_i,b_i]\)需要满足\(sum[b_i]-sum[a_i-1]≥c_i\),这样就可以把原问题变为差分约束问题,同时还需要满足\(0≤sum[i]-sum[i-1]≤1\),这样对该差分约束系统求得最小解,将其转化为≥不等式组跑最长路,答案为\(sum[max\{b_i\}]\)。
差分约束
对于类似\(x_i-x_j≤c_k\)的不等式组,都可以移项变形为\(x_i≤c_k+x_j\),如果令\(c_k=w(i,j)\),\(dis[i]=x_i\),\(dis[j]=x_j\),那么原式变为最短路问题中的松弛操作。
所以可以把\(x_i\)看作是图中的一个结点,对于每个不等式约束\(x_i-x_j≤c_k\),从结点\(j\)到结点\(i\)连一条长度为\(c_k\)的有向边,那么求解差分约束文艺就变成了求解最短(长)路问题了。
代码
#include<iostream>
#include<stdio.h>
#include<queue>
#include<cmath>
#include<string.h>
using namespace std;
int a,b,c;
int t,n,m,tot=0;
int dis[50010],inq[50010],cnt[50010];
int head[250000];
int MAX_b;
int MIN_a;
struct edge {
int to,w,next;
}e[250000];
void add(int u,int v,int w)
{
e[++tot].to = v;
e[tot].w = w;
e[tot].next = head[u];
head[u] = tot;
}
void initial(){
memset(head,-1,sizeof(head));
memset(inq,0,sizeof(inq));
for(int i=0;i<50010;i++){
dis[i] = -1000000;
}
}
void spfa(int s) {
dis[s] = 0;
inq[s] = 1;
queue<int> q;
q.push(s);
while(!q.empty()) {
int u = q.front();
q.pop();
inq[u] = 0;
// cout<<u<<endl;
for(int i=head[u];i!=-1;i=e[i].next) {
int v = e[i].to;
// cout<<u<<","<<v<<","<<e[i].w<<endl;
if(dis[v]<(dis[u]+e[i].w)) {
// cout<<v<<endl;
dis[v] = dis[u] + e[i].w;
if(!inq[v]) {
q.push(v);
inq[v] = 1;
}
}
}
}
}
int main() {
// freopen("in1.txt","r",stdin);
cin>>n;
MAX_b = -10000000;
MIN_a = 10000000;
initial();
for(int i=1;i<=n;i++){
scanf("%d%d%d",&a,&b,&c);
MAX_b = max(MAX_b,b+1);
MIN_a = min(a,MIN_a);
add(a,b+1,c);
}
// cout<<MIN_a<<MAX_b<<endl;
for(int i=MIN_a;i<=MAX_b;i++){
add(i,i+1,0);
add(i+1,i,-1);
// cout<<i<<endl;
}
spfa(MIN_a);
// cout<<"---"<<endl;
cout<<dis[MAX_b]<<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
给出一个符合要求的排名。输出时猫猫的编号之间有空格,最后一名后面没有空格!
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。
Sample Input
4 3
1 2
2 3
4 3
Sample Output
1 2 4 3
思路
由于输赢的方向性,所以最后的排序一定是按照方向性的顺序排列,所以是典型的拓扑排序问题。
由于题目要求按照字典序进行输出名次,所以使用优先级队列即可。
Kahn算法
• 将入度为 0 的点组成一个集合 S
• 每次从 S 里面取出一个顶点 u (可以随便取)放入 L , 然后遍历顶点 u 的所有边 (u, v) , 并删除之,并判断如果该边的另一个顶点 v,如果在移除这一条边后入度为 0 , 那么就将这个顶点放入集合 S 中。不断地重复取出顶点然后重复这个过程……
• 最后当集合为空后,就检查图中是否存在任何边。如果有,那么这个图一定有环路,否者返回 L , L 中顺序就是拓扑排序的结果。
代码
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
//int e[510][510];
int indeg[510],outdeg[510];
int m,n;
vector<int> G[510];
void initial() {
for(int i=0; i<510; i++) {
indeg[i] = 0;
outdeg[i] = 0;
G[i].clear();
}
}
void add(int u,int v) {
G[u].push_back(v);
outdeg[u]++;
indeg[v]++;
}
void Kahn() {
priority_queue<int,vector<int>,greater<int> > q;
for(int i=1; i<=n; i++) {
if(indeg[i]==0) {
// cout<<i<<endl;
q.push(i);
}
}
// cout<<"---"<<endl;
queue<int> ans;
while(q.size()) {
int u = q.top();
q.pop();
ans.push(u);
for(vector<int>::iterator i= G[u].begin(); i!=G[u].end(); i++) {
int v = *i;
indeg[v]--;
if(indeg[v]==0) q.push(v);
}
}
while(ans.size()>1) {
int tmp = ans.front();
ans.pop();
cout<<tmp<<" ";
}
int tmp = ans.front();
ans.pop();
cout<<tmp<<endl;
}
int main() {
// freopen("in2.txt","r",stdin);
bool firstIn = true;
while(cin>>n>>m) {
// if(!firstIn){
// cout<<endl;
// }
firstIn = false;
initial();
int u,v;
while(m) {
m--;
cin>>u>>v;
add(u,v);
}
Kahn();
}
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开始,紧跟着是最高的票数。 接下来一行输出得票最多的同学的编号,用空格隔开,不忽略行末空格!
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
思路
首先通过Kosaraju算法获得SCC图,也就是将那些属于同一个SCC的点看作一类,那么所有直接指向或间接指向该SCC的SCC均是票数,将其相加即可,如果将SCC缩成1个点,那么所有到可达该点的点的票数相加加上该点的票数-自身,即为总票数,可以通过反证法证明最终的答案只会出现在出度为0的SCC点上,而对于可达该点的求解只需要将边反向,求得该点可达的SCC点即可。
坑点
没注意读题,没对比输出。
代码
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int t,m,n,cnt,scnt,st,tans;
int ord[30001],rord[30001];
int scc[30001],dsc[30001],indeg[30001],ans[30001];
bool vis1[30001],vis2[30001],vis[30001];
vector<int> G1[30001],G2[30001],G3[30001];
void initial() {
cnt = 0;
st = 0;
for(int i=0; i<=30000; i++) {
G1[i].clear();
G2[i].clear();
G3[i].clear();
vis1[i] = 0;
vis2[i] = 0;
vis[i] = 0;
indeg[i] = 0;
scc[i] = 0;
dsc[i] = 0;
ord[i] = 0;
rord[i] = 0;
ans[i] = 0;
}
}
void dfs1(int s) {
vis1[s] = true;
for(vector<int>::iterator i= G1[s].begin(); i!=G1[s].end(); i++) {
int d = *i;
if(!vis1[d]) {
dfs1(d);
}
}
cnt++;
ord[s] = cnt;
rord[cnt] = s;
}
void dfs2(int s) {
vis2[s] = true;
dsc[s] = st;
for(vector<int>::iterator i= G2[s].begin(); i!=G2[s].end(); i++) {
int d = *i;
if(!vis2[d]) {
scnt++;
dfs2(d);
}
}
}
void dfs(int s) {
vis[s] = true;
// cout<<s<<endl;
tans += scc[s];
for(vector<int>::iterator i= G3[s].begin(); i!=G3[s].end(); i++) {
int d = *i;
// cout<<d<<endl;
if(!vis[d]) {
dfs(d);
}
}
}
int main() {
// freopen("in3.txt","r",stdin);
cin>>t;
int co = 0;
while(t) {
t--;
co ++;
cin>>n>>m;
int a,b;
initial();
for(int i=0; i<m; i++) {
scanf("%d%d",&a,&b);
G1[a+1].push_back(b+1);
G2[b+1].push_back(a+1);
// cout<<a+1<<","<<b+1<<endl;
}
for(int i=1; i<=n; i++) {
if(!vis1[i]) dfs1(i);
}
for(int i=n; i>=1; i--) {
if(!vis2[rord[i]]) {
// cout<<"ind:"<<rord[i]<<endl;
scnt = 1;
st++;
dfs2(rord[i]);
scc[st] = scnt;
}
}
// for(int i=1; i<=st; i++) {
// cout<<scc[i]<<endl;
// }
for(int s=1; s<=n; s++) {
for(vector<int>::iterator i= G2[s].begin(); i!=G2[s].end(); i++) {
int d = *i;
// cout<<s<<","<<d<<endl;
// cout<<dsc[s]<<","<<dsc[d]<<":";
if(dsc[s]!=dsc[d]) {
int u = dsc[s],v = dsc[d];
// cout<<u<<","<<v<<endl;
indeg[v]++;
G3[u].push_back(v);
}
}
}
for(int i=1; i<=st; i++) {
if(indeg[i]==0) {
tans = 0;
for(int j=0;j<5001;j++) vis[j] = 0;
dfs(i);
ans[i] = tans - 1;
}
}
int MaxAns = 0;
queue<int> ansq;
for(int i=1; i<=st; i++) {
// cout<<ans[i]<<",";
MaxAns = max(MaxAns,ans[i]);
}
// cout<<endl;
for(int i=1; i<=st; i++) {
if(MaxAns==ans[i]) {
ansq.push(i);
}
}
priority_queue<int,vector<int>,greater<int> > output;
while(ansq.size()) {
int tmp = ansq.front();
ansq.pop();
for(int i=1; i<=n; i++) {
if(dsc[i]==tmp) output.push(i-1);
}
}
printf("Case %d: %d\n",co,MaxAns);
while(output.size()>1){
printf("%d ",output.top());
output.pop();
}
printf("%d\n",output.top());
}
return 0;
}