A-区间选点Ⅱ
题目描述
给定一个数轴上的 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个区间[ai,bi]需要满足sum[bi]-sum[ai-1]>=ci,即sum[bi]>=ci+sum[ai-1],ai-1指向bi的边权重为ci,每次存储边,同时更新右端点
同时还需要保证sum是有意义的
- 0<=sum[i]-sum[i-1]<=1,i-1指向i的边权重为0,i指向i-1的边权重为-1,从0至右端点,对每个单位区间添加这两条边
求解该差分约束系统的最小解,转化为 >= 不等式组用spfa跑最长路,答案为sum[max{bi}],b_max为右端点。
注意
因为存在不等式0<=sum[0]-sum[-1]<=1,所以将区间整体右移一个单位.
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=1e8;
const int N=50110;
const int M=50010*4;
int n,dis[N],head[N],vis[N],tot,b_max=0;
queue<int> q;
struct Edge {
int to,nxt,w;
}e[M];
void add(int x,int y,int w) {
e[++tot].to=y;
e[tot].nxt=head[x];
e[tot].w=w;
head[x]=tot;
}
void init() {
for(int i=0;i<=50010;++i) {
dis[i]=-inf;
vis[i]=0;
}
tot=0;
}
void spfa(int s) {
q.push(s);
dis[s]=0;
vis[s]=1;
while(!q.empty()) {
int u=q.front();q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to;
if(dis[v]<dis[u]+e[i].w) {
dis[v]=dis[u]+e[i].w;
if(!vis[v]) {
q.push(v);
vis[v]=1;
}
}
}
}
}
int main() {
scanf("%d",&n);
int a=0,b=0,c=0;
init();
for(int i=1;i<=n;++i) {
scanf("%d%d%d",&a,&b,&c);
a += 1,b += 1;
add(a-1,b,c);
if(b_max<b) b_max=b;
}
for(int i=1;i<=b_max;++i) {
add(i,i-1,-1);
add(i-1,i,0);
}
spfa(0);
printf("%d\n",dis[b_max]);
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的点加入队列,由于编号小的先输出,使用优先队列,设置为小根堆。
每次从堆中取出一个顶点u,加入ans,然后遍历u的所有边(u,v),并删除(即v的in_deg–),如果移除后邻接点v入度为0,将v加入堆中,不断重复上述过程,直到所有点加入ans即可。
代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
int n,m,in_deg[510],head[510],tot;
vector<int> ans;
priority_queue<int,vector<int>,greater<int>> q;
struct Edge{
int to,nxt;
}e[50010];
void add(int u,int v) {
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
}
void init() {
for(int i=0;i<=n;++i) {
head[i]=-1;
in_deg[i]=0;
}
while(!q.empty()) q.pop();
ans.clear();
tot=0;
}
int main() {
int x=0,y=0;
while(scanf("%d%d",&n,&m)!=EOF) {
init();
while(m--) {
scanf("%d%d",&x,&y);
add(x,y);
in_deg[y]++;
}
for(int i=1;i<=n;++i)
if(in_deg[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=e[i].nxt) {
int v=e[i].to;
in_deg[v]--;
if(in_deg[v]==0) q.push(v);
}
}
for(int i=0;i<ans.size()-1;++i)
printf("%d ",ans[i]);
printf("%d\n",ans[ans.size()-1]);
}
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
解题思路
有向图SCC并缩点,互相可达与单向可达分开考虑。
对于属于第i个SCC的点来说,答案分为两部分(SCC[i]为第i个SCC中点的个数)——
- 当前SCC中的点,ans+=SCC[i]-1(去除自己)
- 其他SCC中的点,sum(SCC[j]),j可到达i
这种思路下很容易想到,得票最多一定出现在出度为0的SCC中!
那么在反图中,对每个入度为0的点dfs,累加能达到点的SCC[i],即可得到答案。
具体做法:
用Kosaraju求SCC
① 第一遍dfs遍历原图,得到原图的后序序列dfn
② 对后序序列从n开始逆向对所有点第二遍dfs(相当于对逆后序序列从头开始),c[i]中存储原始点i所属的SCC编号,SCC[i]为编号为i的SCC中点的个数
缩点
将同一个SCC的点合并成一个点,遍历反图中的每个点的邻接边,如果边的两端点不属于同一SCC则在缩点图中加入该边,加入边时,计数每个SCC入度,最终形成新的缩点图。
计算每个SCC获得的票数存入ans[i]
由题可知:同一SCC中的点获得票数相同为SCC-1
遍历缩点后的图,对于入度为0的点(缩点后的图源于反图,所以在原图就是出度为0的点,所有可以到达它的点,都给它投票),进行第三遍dfs计算每个缩点总共获得的票数(去除自己)
计算最大得票数max_ans
遍历原图的所有点,如果某点在所在的SCC的ans==max_ans,则输出该点
注意
原题序号为0-N-1,图中为1-N,因此最后应输出i-1。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int t,n,m,tot1,tot2,tot,dcnt,scnt,sum,max_ans;
int h_org[5010],h_inv[5010],h_ctr[5010],vis[5010];
int dfn[5010],c[5010],scc[5010],in_deg[5010],ans[5010],s_vis[5010];
struct Edge {
int to,nxt;
}e_org[30010],e_inv[30010],e_ctr[30010];
void add1(int u,int v) {//原图
e_org[++tot1].to=v;
e_org[tot1].nxt=h_org[u];
h_org[u]=tot1;
}
void add2(int u,int v) {//反图
e_inv[++tot2].to=v;
e_inv[tot2].nxt=h_inv[u];
h_inv[u]=tot2;
}
void add(int u,int v) {//缩点图
e_ctr[++tot].to=v;
e_ctr[tot].nxt=h_ctr[u];
h_ctr[u]=tot;
in_deg[v]++;
}
void dfs1(int x) {
vis[x]=1;
for(int i=h_org[x];i;i=e_org[i].nxt) {
int y=e_org[i].to;
if(!vis[y]) dfs1(y);
}
//后序
dfn[++dcnt]=x;
}
void dfs2(int x) {
c[x]=scnt;
scc[scnt]++;
for(int i=h_inv[x];i;i=e_inv[i].nxt) {
int y=e_inv[i].to;
if(!c[y]) dfs2(y);
}
}
void dfs(int x) {
s_vis[x]=1;
sum+=scc[x];
for(int i=h_ctr[x];i;i=e_ctr[i].nxt) {
int y=e_ctr[i].to;
if(!s_vis[y]) dfs(y);
}
}
void kosaraju() {
dcnt=0,scnt=0;
memset(c,0,sizeof c);
memset(vis,0,sizeof vis);
//正向遍历
for(int i=1;i<=n;++i)
if(!vis[i]) dfs1(i);
//逆后序;
for(int i=n;i>=1;--i)
if(!c[dfn[i]]) ++scnt,dfs2(dfn[i]);
}
void shrink() {//缩点
for(int x=1;x<=n;++x) {
for(int i=h_inv[x];i;i=e_inv[i].nxt) {
int y=e_inv[i].to;
if(c[x]==c[y]) continue;
add(c[x],c[y]);
}
}
}
void init() {
for(int i=0;i<=5010;++i) {
h_org[i]=0;h_inv[i]=0;h_ctr[i]=0;
dfn[i]=0;in_deg[i]=0;scc[i]=0;ans[i]=0;
}
sum=0;max_ans=0;tot1=0;tot2=0;tot=0;
}
int main() {
scanf("%d",&t);
int a=0,b=0;
for(int k=1;k<=t;++k) {
scanf("%d%d",&n,&m);
init();
for(int i=1;i<=m;++i) {
scanf("%d%d",&a,&b);
add1(a+1,b+1);
add2(b+1,a+1);
}
kosaraju();
shrink();
for(int i=1;i<=scnt;++i) {
if(!in_deg[i]) {
sum=0;
//每个入度为0的连通分量都要dfs
memset(s_vis,0,sizeof s_vis);
dfs(i);
ans[i]=sum-1;
if(max_ans<ans[i]) max_ans=ans[i];
}
}
printf("Case %d: %d\n",k,max_ans);
int flag=0;
for(int i=1;i<=n;++i) {
if(ans[c[i]]==max_ans) {
if(!flag) {
printf("%d",i-1);
flag++;
}
else printf(" %d",i-1);
}
}
printf("\n");
}
return 0;
}