A-区间选点
题干
思路
由题干可知该题利用差分约束,
每个区间看作一条边,
sum[ai—bi]=sum[bi]-sum[ai-1]>= ci
即sum[bi]>=sum[ai-1]+ci
dis[bi]>=dis[ai-1]+Wab
同时dis[i]-dis[i-1]>=0
dis[i-1]-dis[i]>=-1
即dis[i]>=dis[i-1]+0
dis[i-1]>=dis[i}-1
所以可以将[i-1,i]看作一条权值为0的边
将[i,i-1]看作一条权值为-1的边
这次取最长路径,也就是相同位置dis最大
代码
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,a,b,c;//区间个数,区间端点和权值
int dis[50010],vis[50010],l,r,tot,head[50010];
//存储最大距离,标记数组,区间左端点、右端点,head数组索引;
int cot[50010],vi[50010];
long long int inf=1e9;
queue <int> q;
struct edge{
int to,next,w;
}edges[500100];//存储所有数组
void chushi()
{
tot=0;
for(int i=0;i<50010;i++)
{
dis[i]=-inf;
vis[i]=0;
head[i]=-1;
cot[i]=0;
vi[i]=0;
}
}
void add(int x,int y,int w)
{
edges[tot].to=y;
edges[tot].next=head[x];
edges[tot].w=w;
head[x]=tot;
tot++;
//cout<<"加边"<<endl;
}
void dfs(int s)//找到所有与负环相连的点,这些点都没有最短距离
{
vi[s]=1;
for(int i=head[s];i!=-1;i=edges[i].next)
{
if(vi[edges[i].to]==0)
dfs(edges[i].to);
}
}
void SPFA()
{
//cout<<1<<endl;
while(!q.empty())
q.pop();
dis[l-1]=0;
vis[l-1]=1;
q.push(l-1);
//cout<<1<<endl;
while(!q.empty())
{
//cout<<2<<endl;
int x=q.front();
q.pop();
//cout<<3<<endl;
vis[x]=0;
for(int i=head[x];i!=-1;i=edges[i].next)
{
//cout<<4<<endl;
int y=edges[i].to;
if(vi[y]!=1)
{
if(dis[y]<dis[x]+edges[i].w)
{
//cout<<5<<endl;
cot[y]=cot[x]+1;
if(cot[y]>n)
dfs(y);
dis[y]=dis[x]+edges[i].w;
//cout<<6<<endl;
if(!vis[y])
{
q.push(y);
vis[y]=1;
//cout<<7<<endl;
}
//cout<<8<<endl;
}
}
//cout<<9<<endl;
}
//cout<<10<<endl;
}
//return;
}
int main()
{
scanf("%d",&n);
chushi();
l=50010;
r=-1;
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
if(a<l)
l=a;
if(b>r)
r=b;
add(a,b+1,c);
}
l++;
r++;
//cout<<l<<" "<<r<<endl;
for(int i=l;i<=r;i++)
{
add(i-1,i,0);
add(i,i-1,-1);
}
/*for(int k=0;k<n+r-l+1;k++)
{
cout<<edges[k].to<<endl;
}*/
//cout<<1<<endl;
SPFA();
//cout<<"SPAT结束\n"<<endl;
int ans=dis[r];
printf("%d",ans);
return 0;
}
猫猫向前冲
题干
思路
用拓扑排序,首先把每一只猫当做一个点,每一个前后顺序当做一条边,然后构建有向无权图(head和edges[])。
然后用一个优先队列(因为要输出字典序最小的拓扑序列)存储图中所有入度为0的点,然后用遍历每个入度为0的点,遍历完就在队列中删去,将其放入输出队列。遍历的时候每个邻点入度减一,当其入度为0时也放入优先队列,直到最后优先队列为空。
代码
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,p1,p2;
int ru[505],head[505];
queue<int>q1;//存储输出序列
priority_queue<int>q2;//存储入度为零的点
struct edge{
int to,next;
}edges[1010];
int tot;
void chushi()
{
tot=0;
for(int i=0;i<505;i++)
{
ru[i]=0;
head[i]=-1;
}
}
void add(int a,int b)
{
ru[b]++;
edges[tot].to=b;
edges[tot].next=head[a];
head[a]=tot;
tot++;
}
void kahn()
{
while(!q2.empty())
{
int x=-q2.top();//由于是大根堆,所以所有数都是存的相反数
q2.pop();
q1.push(x);
for(int i=head[x];i!=-1;i=edges[i].next)
{
ru[edges[i].to]--;
if(ru[edges[i].to]==0)
q2.push(-edges[i].to);
}
}
}
int main()
{
while(scanf("%d%d",&n,&m))
{
chushi();
for(int i=0;i<m;i++)
{
scanf("%d%d",&p1,&p2);
add(p1,p2);
}
for(int i=1;i<=n;i++)
{
if(!ru[i])
q2.push(-i);
}
kahn();
int num=q1.size();
int ans;
ans=q1.front();
q1.pop();
printf("%d",ans);
//cout<<ans;
for(int i=1;i<num;i++)
{
ans=q1.front();
q1.pop();
printf(" %d",ans);
//cout<<" "<<ans;
}
/* while(q1.size())
{
ans=q1.front();
q1.pop();
printf(" ");
printf("%d",ans);
}*/
//printf("\n");
cout<<endl;
}
return 0;
}
C-班长竞选
题干
思路
这个题用强连通分量
首先把人当做点,选举关系当做边,构造原图和反图(head、edges[]);
然后对原图的每个点进行一次dfs,得到所有点的逆后序(递归返回顺序的逆);
之后在反图上按照逆后序遍历每个点dfs,将同一个环的元素用同一个sc值标记,用sum【sc】表示每个scc的点个数;
然后缩点,sc的大小就是scc的个数,在第二次dfs时,当一个环遍历结束的时候,他遇到的下一个元素dis标记一定为1,此时判断这个点的sc值是否等于当前环的sc值,若不相等,说明scc的反图里有一条这样的边,在加完边之后还要注意重复,我用一个二维数组来标记,避免重复加边。
最后找票数最多的人,就是scc反图入度为0的人的所有个数,此时进行第三次dfs,所有与该scc相连的scc的点个数加上他本身的点个数再减去1,就是最后的值。最后再把所有入度为0的scc的值进行比较,找一个/若干个最大的输出。
代码
#include<iostream>
#include<queue>
//#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
int T,n,m,a,b,tot;
int ni=0;//逆序索引
int sc=0;//scc的索引
int head1[5005], vis[5005],nih[5005],nihx[5005];
int head2[5005],scc[5005],sum[5005];
int head3[5005],ru[5005];//scc反图,入度
int vi[5005][5005];//标记scc图的边,防止重复插入边
int sum2[5005];
//queue<int>q;
struct edge{
int to,next;
}edges1[10010],edges2[10010],edges3[10010];
void add(int x,int y)
{
edges1[tot].to=y;
edges1[tot].next=head1[x];
head1[x]=tot;
edges2[tot].to=x;
edges2[tot].next=head2[y];
head2[y]=tot;
tot++;
} //构建正反图
void adsc(int x,int y)
{
edges3[++tot].to=y;
edges3[tot].next=head3[x];
head3[x]=tot;
ru[y]++;
tot++;
}//x,y都是scc的索引
void dfs1(int s)
{
/*for(int i=0;i<=n;i++)
{
vis[i]=0;
}*/
vis[s]=1;//标记初始化
//head
for(int i=head1[s];i!=-1;i=edges1[i].next)
{
int x=edges1[i].to;
if(vis[x]==0)
dfs1(x);
}
ni++;
nih[s]=ni;
}//第一次正图dfs,将每个点遍历,得到逆后序列
void tranni()
{
for(int i=0;i<=n;i++)
{
for(int j=0;j<=n;j++)
{
if(j==nih[i])
nihx[j]=i;
}
}
}//将逆序序列正向存储到nihx里
void dfs2(int s)
{
//cout<<2<<endl;
vis[s]=1;
scc[s]=sc;//一个环的用一个sc
sum[sc]++;//记录同一个scc的点数
//cout<<sum[sc]<<endl;
for(int i=head2[s];i!=-1;i=edges2[i].next)
{
int x=edges2[i].to;
if(vis[x]==0)
dfs2(x);
else//当该点被标记过,要么是同一个scc,要么是其他的scc
{
if(scc[x]!=sc&&vi[sc][scc[x]]==0)//当是其他scc时,将边的关系插入新的scc反图
{
adsc(sc,scc[x]);
vi[sc][scc[x]]=1;
}
}
}
//sc++;
} //根据逆后序列dfs反图 ,将同一个环的scc的点标记为同一个数字
void kosaraju()
{
for(int i=0;i<5005;i++)
{
vis[i]=0;
nih[i]=0;
nihx[i]=0;
}
ni=0;
for(int j=0;j<n;j++)
{
if(vis[j]==0)
dfs1(j);
}
//for(int i=0;i<n;i++)
//cout<<nih[i]<<endl;
tranni();//得到后序nihx
//for(int i=1;i<=n;i++)
//cout<<nihx[i]<<endl;
for(int i=0;i<5005;i++)
{
vis[i]=0;
sum[i]=0;
head3[i]=-1;
}
//cout<<sum[0]<<endl;
for(int i=0;i<5005;i++)
for(int j=0;j<5005;j++)
vi[i][j]=0;
//cout<<vi[0][0]<<endl;
tot=0;
sc=0;
for(int i=n;i>0;i--)
{
if(vis[nihx[i]]==0)
{
dfs2(nihx[i]);
sc++;
}
}
//for(int i=0;i<n;i++)
//cout<<scc[i]<<endl;
//for(int i=0;i<sc;i++)
//cout<<sum[i]<<endl;
}
int dfs3(int s)
{
vis[s]=1;
int ans=sum[s];
for(int i=head3[s];i!=-1;i=edges3[i].next)
{
int x=edges3[i].to;
if(vis[x]==0)
ans=ans+dfs3(x);
}
return ans;
}
int main()
{
scanf("%d",&T);
int num=1;
while(T--)
{
scanf("%d%d",&n,&m);
tot=0;
for(int i=0;i<=n;i++)
{
head1[i]=-1;
head2[i]=-1;
ru[i]=0;
sum2[i]=0;
}
while(m--)
{
scanf("%d%d",&a,&b);
add(a,b);
//addf(b,a);
}
tot=0;
kosaraju();
int ant=-1;
//cout<<sc<<endl;
for(int i=0;i<sc;i++)
{
//cout<<"*****"<<ru[i]<<endl;
if(ru[i]==0)
{
//cout<<"&&&&&&&&"<<i<<endl;
for(int i=0;i<=n;i++)
vis[i]=0;
sum2[i]=dfs3(i)-1;
cout<<sum2[i]<<endl;
ant=max(ant,sum2[i]);
}
}
printf("Case %d: %d\n",num,ant);
int f=0;
for(int i=0;i<n;i++)
{
if(sum2[scc[i]]==ant)
{
if(f==0)
printf("%d",i);
else
printf(" %d",i);
f++;
}
}
printf("\n");
num++;
}
return 0;
}
订正
在提交的时候我发现用以上这种方法空间复杂度太高,内存不够,在查询资料之后我发现比较普遍的算法是用vector数组,他可以用vector h[x][i]存储所有与点x相邻的点,比用edges[]数组存储临边关系要方便很多。
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int T,n,m,a,b;
int c[5005],vis[5005],nih[5005],ni,sc; //scc标记数组,dfs遍历数组,存储逆后序数组
vector<int> h1[5005],h2[5005],h3[5005];//原图,反图,缩点后新图
int ru[5005],scc[5005],sum[5005];//缩点图入度
void dfs1(int x)
{
//cout<<1<<endl;
vis[x]=1;
for(int i=0;i<h1[x].size();i++)
{
if(!vis[h1[x][i]])
dfs1(h1[x][i]);
}
nih[++ni]=x;
}//第一次dfs,得到逆后序
void dfs2(int x)
{
//vis[x]=1;
c[x]=sc;
for(int i=0;i<h2[x].size();i++)
{
if(!c[h2[x][i]])
dfs2(h2[x][i]);
}
}//第二次dfs,把同一个环的用c标记为一个sc
void kosaraju()
{
//cout<<"kosaraju"<<endl;
ni=-1;
sc=0;
for(int i=0;i<5005;i++)
{
c[i]=0;
vis[i]=0;
nih[i]=0;
}
//cout<<"IIIIIIIII"<<endl;
//cout<<n<<endl;
for(int j=0;j<n;j++)
{
// cout<<"&&&&&&&&&&"<<endl;
if(vis[j]==0)
dfs1(j);
}
//cout<<"***********"<<endl;
//for(int i=0;i<n;i++)
// cout<<nih[i]<<endl;
//for(int i=0;i<5005;i++)
// vis[i]=0;
for(int i=n-1;i>=0;i--)
{
if(!c[nih[i]])
{
sc++;
dfs2(nih[i]);
}
}
}//完成前两次循环,得到一个个scc
int dfs3(int x)
{
vis[x]=1;
int ans=scc[x];
for(int i=0;i<h3[x].size();i++)
{
if(!vis[h3[x][i]])
ans=ans+dfs3(h3[x][i]);
}
return ans;
}
int main()
{
int num=1;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
//cout<<"NNNNNNNNNNNNN"<<endl<<n<<endl;
int k=0;
int r=n;
while(r--)
{
h1[k].clear();
h2[k].clear();
k++;
}
for(int i=0;i<5005;i++)
{
ru[i]=0;
scc[i]=0;
sum[i]=0;
}
//cout<<"abababababab"<<endl;
while(m--)
{
scanf("%d %d",&a,&b);
h1[a].push_back(b);
h2[b].push_back(a);
//cout<<a<<" "<<b<<endl;
}
kosaraju();
//for(int i=0;i<n;i++)
//{
// cout<<c[i]<<endl;
//}
for(int i=0;i<n;i++)
scc[c[i]]++; //索引是sc,里面放的是每个sc的集合数
for(int i=0;i<=sc;i++)
{
ru[i]=0;
h3[i].clear();
}
for(int i=0;i<n;i++)
{
for(int j=0;j<h1[i].size();j++)//遍历每个点的临边
{
if(c[i]!=c[h1[i][j]])//当两个点不在一个scc中时,将这两个点所在sc加入新的图里
{
h3[c[h1[i][j]]].push_back(c[i]);
ru[c[i]]++;
}
}
}
int cnt=-1;
for(int i=1;i<=sc;i++)
{
if(ru[i]==0)
{
for(int j=0;j<=sc;j++)
vis[j]=0;
sum[i]=dfs3(i)-1;
cnt=max(cnt,sum[i]);
}
}
printf("Case %d: %d\n",num,cnt);
int f=0;
for(int i=0;i<n;i++)
{
if(sum[c[i]]==cnt)
{
if(f==0)
printf("%d",i);
else
printf(" %d",i);
f++;
}
}
printf("\n");
num++;
}
return 0;
}