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
做法
对于每一段[a,b],由于a,b均是从0开始取值,为了后续简便操作,
设置sum[a]为0到a-1区间的撒点的个数。
因此,对于每一个区间[a,b],要求点的个数>=c,因此,需要sum[b+1]-sum[a]>=c,且每个点,1>sum[i+1]-sum[i]>0.
又因为,要输出总的整数点最小。
构造图的时候,求得是边界条件。因此,跑一次最长路即可找到最小整数点所对应的答案。
即为sum[max{右边界+1}]
代码
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
const int N=600000;
const int M=600000;
const int inf=1e9;
int n,a,b,c,tot;
struct edge{
int to,next,w;
}e[M];
int head[N],vis[N],dis[N];
queue<int> q;
void add(int x,int y,int w)
{
e[++tot].to=y;
e[tot].w=w;
e[tot].next=head[x];
head[x]=tot;
}
int main()
{
scanf("%d",&n);
int max=0;
while(n--)
{
scanf("%d %d %d",&a,&b,&c);
add(a,b+1,c);
if(b+1>max)max=b+1;
}
for(int i=1;i<=max;i++)
{
add(i-1,i,0);
add(i,i-1,-1);
}
while(!q.empty())q.pop();
for(int i=0;i<=max;i++)
{
dis[i]=-inf;
vis[i]=0;
}
q.push(0);
dis[0]=0;
vis[0]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
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;
}
}
}
}
printf("%d",dis[max]);
}
总结
1、差分约束转化的条件。
2、贪心做法:week3
每次选最靠右的点
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
做法
拓扑排序。
每次维护一个入度为0的点的优先级队列。
每次将队首位置的点删除并加入结果时,检查其每个出点,看是否导致其入度为0,若为0,则加入优先队列。
代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=600;
const int M=6000;
const int inf=1e9;
int n,m,a,b,c,tot;
struct edge{
int to,next;
}e[M];
int head[N],vis[M],dis[N],in_deg[N];
priority_queue<int> q;
void add(int x,int y)
{
e[++tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void toposort()
{ vector<int> ans;
while(!q.empty())q.pop();
for(int i=1;i<=n;i++)
if(in_deg[i]==0)q.push(-i);
while(!q.empty())
{
int x=-q.top();
q.pop();
ans.push_back(x);
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if(!vis[i])
{
if(--in_deg[y]==0)q.push(-y);
vis[i]=1;
}
}
}
if(ans.size()==n)
{
for(int i=0;i<n-1;i++)
{
cout<<ans[i]<<" ";
}
cout<<ans[n-1]<<endl;
}
}
int main()
{
while(~scanf("%d %d",&n,&m))
{ memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
memset(in_deg,0,sizeof(in_deg));
tot=0;
for(int k=1;k<=m;k++)
{
scanf("%d %d",&a,&b);
add(a,b);
in_deg[b]++;
}
toposort();
}
}
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
做法
本题的答案分为两种可能性。一个是同一个强联通分量里,所有的点均是答案。不同强联通分量若票数相同,则这俩强联通分量里所有点也都是答案。
首先,求强连通分量(储存正向和反向边)。
其次,缩点。将每个强联通分量缩成一个点,并把原强联通分量间的边连上。
注意到,缩点后可能会有重复边,这里用set去重后再加入。
为了方便,我们在构建缩点的时候,边集合用反图的形式存入(便于后续dfs)
对于新构建的边集,我们找到入度为0的点(即原图的出度为零)。然后dfs遍历求它的票数。
票数=scc(自己的个数)-1+ sum((scc(可以dfs的点的个数))
找到最大max并记录即可。
由于是从小到大排列。
对所有点遍历一遍输出即可。
代码
#include<iostream>
#include<queue>
#include<cstring>
#include<cmath>
#include<set>
#include<cstdio>
using namespace std;
const int N=6000;
const int M=35000;
int n,m,a,b,t,tot1,tot2,tot3;
struct edge{
int from,to,next;
}e[M],e2[M],e3[M],e4[M];
int head1[N],head2[N],head3[N],c[N],vis[N],vis2[N],dfn[N],scc[N],count[N],ans[N],dcnt,scnt;
set<pair<int,int>> s;
//dfn dfs后序第i号是哪个点
//c[i] 第i号点的scc编号
void add1(int x,int y){
e[++tot1].to=y;
e[tot1].next=head1[x];
head1[x]=tot1;
}
void add2(int x,int y){
e2[++tot2].to=y;
e2[tot2].next=head2[x];
head2[x]=tot2;
}
void add3(int x,int y){
e3[++tot3].to=y;
e3[tot3].next=head3[x];
head3[x]=tot3;
count[y]++;
}
void dfs1(int x){
vis[x]=1;
for(int i=head1[x];i;i=e[i].next){
int y=e[i].to;
if(!vis[y]) dfs1(y);
}
dfn[++dcnt]=x;
}
void dfs2(int x){
c[x]=scnt;
scc[scnt]++;
// cout<<scnt<<"SSS"<<endl;
for(int i=head2[x];i;i=e2[i].next){
int y=e2[i].to;
if(!c[y]) dfs2(y);
}
}
void dfs(int x,int type){
vis2[x]=1;
for(int i=head3[x];i;i=e3[i].next){
int y=e3[i].to;
if(!vis2[y]){
vis2[y]=1;
ans[type]+=scc[y];
dfs(y,type);
}
}
}
void kosaraju()
{
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]);
//缩点
//cout<<"Aaa"<<endl;
memset(vis2,0,sizeof(vis2));
for(int i=0;i<n;i++){
for(int j=head1[i];j;j=e[j].next){
int y=e[j].to;
if(c[i]!=c[y]){
s.insert({c[y],c[i]});
}
}
}
for(auto &x:s){
add3(x.first,x.second);
}
for(int i=1;i<=scnt;i++){
if(count[i]==0){
memset(vis2,0,sizeof(vis2));
ans[i]+=scc[i];
dfs(i,i);
}
}
int max=0;
for(int i=1;i<=scnt;i++){
if(max<ans[i]) max=ans[i];
}
for(int i=1;i<=scnt;i++){
//cout<<ans[i]<<"**"<<endl;
}
printf("%d\n",max);
bool bj=false;
for(int i=0;i<n;i++)
{
if(ans[c[i]]==max)
{if(bj==true)printf(" ");
printf("%d",i);
bj=true;
}
}
printf("\n");
}
int main()
{ scanf("%d",&t);
for(int i=1;i<=t;i++)
{scanf("%d %d",&n,&m);
tot1=tot2=tot3=0;
dcnt=scnt=0;
s.clear();
memset(head1,0,sizeof(head1));
memset(head2,0,sizeof(head2));
memset(head3,0,sizeof(head2));
memset(scc,0,sizeof(scc));
memset(c,0,sizeof(c));
memset(vis,0,sizeof(vis));
memset(count,0,sizeof(count));
memset(ans,-1,sizeof(ans));
for(int k=1;k<=m;k++)
{
scanf("%d %d",&a,&b);
add1(a,b);
add2(b,a);
}
printf("Case %d: ",i);
kosaraju();
}
}
总结
本题较为复杂,写的时候思路不清晰改了好几遍。
以后还是模块化自顶向下步骤清晰些好。