【笔记】拓扑排序(Ⅱ)
题单
0X00 P7860 [COCI2015-2016#2] ARTUR
好题。
首先考虑本题与拓扑排序有和关系。可以想到,某些棍子的先后移动顺序是有限制的。比如:
这里红色的必须比蓝色的先移动,因为它们在
所以,棍子两两之间可能存在限制关系,这就符合拓扑排序的条件了。考虑根据每一对限制关系建边。若
其次,也是较麻烦的一部分,就是如何根据两线段的坐标判断其移动先后限制。
为了方便,在读入时判断并交换好,用
- 没有限制关系,返回
比 要先移动,返回 比 要先移动,返回
为了方便,设
首先看
肉眼可见,
然后看一般情况。多画几个图,可以发现,只需要比较
至于如何求函数值。。上过初中数学都会。程序中变量名都遵从
然鹅,这样写获得了
还有一种比较坑的情况,就是
这时候函数
Code:
#include<bits/stdc++.h>
using namespace std;
int n,m,in[5005];
struct node{
int x1,x2,y1,y2;
}b[5005];
vector<int> a[5005];
queue<int> q;
int check(node u,node v){ //0:无关,-1:先移u,1:先移v
int op=1;
if(u.x1>v.x1) swap(u,v),op=-op;
if(u.x2<v.x1) return 0;
double K,B,tmp;
if(!(u.x2-u.x1)){
K=1.0*(v.y2-v.y1)/(v.x2-v.x1);
B=(double)v.y1-K*v.x1;
tmp=K*u.x1+B;
if(u.y1>tmp) return op;
return -op;
}
K=1.0*(u.y2-u.y1)/(u.x2-u.x1);
B=(double)u.y1-K*u.x1;
tmp=K*v.x1+B; //求函数
if(tmp>v.y1) return op;
return -op;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&b[i].x1,&b[i].y1,&b[i].x2,&b[i].y2);
if(b[i].x1>b[i].x2) swap(b[i].x1,b[i].x2),swap(b[i].y1,b[i].y2);
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++){
int op=check(b[i],b[j]);
if(op==-1) a[i].push_back(j),in[j]++;
if(op==1) a[j].push_back(i),in[i]++; //连边
}
}
for(int i=1;i<=n;i++) if(!in[i]) q.push(i),printf("%d ",i);
while(q.size()){ //拓扑
int k=q.front();
q.pop();
for(int i=0;i<a[k].size();i++){
int tmp=a[k][i];
in[tmp]--;
if(!in[tmp]) q.push(tmp),printf("%d ",tmp);
}
}
return 0;
}
0X01 P1983 [NOIP2013 普及组] 车站分级
考虑以车站等级为限制要求来建边。
以一趟从
比如样例一中 1 3 5 6
:
考虑到站点较多,可能重复建边,所以每次建边标记一下。
最后答案即为图的层数。
Code:
#include<bits/stdc++.h>
using namespace std;
int n,m,in[1005],x,y[1005],dep[1005],ans;
bool mark[1005][1005],fl[1005];
vector<int> a[1005];
queue<int> q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d",&x);
for(int j=1;j<=n;j++) fl[j]=0;
for(int j=1;j<=x;j++) scanf("%d",&y[j]),fl[y[j]]=1;
for(int j=y[1];j<=y[x];j++){
if(fl[j]) continue;
for(int k=1;k<=x;k++){
if(mark[j][y[k]]) continue;
a[j].push_back(y[k]);
in[y[k]]++;
mark[j][y[k]]=1;
}
}
}
for(int i=1;i<=n;i++){
if(!in[i]) q.push(i),dep[i]=1;
}
while(q.size()){
int k=q.front();q.pop();
for(int i=0;i<a[k].size();i++){
int tmp=a[k][i];
dep[tmp]=max(dep[tmp],dep[k]+1);
in[tmp]--;
if(!in[tmp]) q.push(tmp);
}
}
for(int i=1;i<=n;i++) ans=max(ans,dep[i]);
printf("%d",ans);
return 0;
}
0X02 P1347 排序
因为题目要求合法就输出,所以要边输入边判断,也就是每次都跑一遍拓扑。
还好数据范围不大,完全可以。
一共存在三种情况:
- 可以确定顺序,立刻输出
- 非法,立刻停止
- 最终也无法确定顺序
这里注意,可以确定顺序 不是存在合法拓扑序,而是要合法且唯一,这就意味着吗,每个点都要在单独的一层。
非法 就是存在环,用常规判环方法,如果一次拓扑遍历到的节点少于 当前应该有的节点 ——注意是当前,每次要数已经有几个节点了——那么久非法了,直接输出并结束。
如果一次遍历到的节点达到了
如果到最后既没有非法也没有合法,就是无法确定。
还有,每次拓扑的时候要先把
以下程序中
Code:
#include<bits/stdc++.h>
using namespace std;
int n,m,in[30],ans[30],cnt,dep[30],mx,kk;
bool mark[30];
string s;
vector<int> a[30];
queue<int> q;
int topo(){
while(q.size()) q.pop();
memset(ans,0,sizeof(ans));
memset(dep,0,sizeof(dep));
mx=kk=0;
int iin[30];
for(int i=0;i<26;i++) if(mark[i]) iin[i]=in[i],kk++;
for(int i=0;i<26;i++){
if(!iin[i]&&mark[i]) q.push(i),dep[i]=1;
}
int cnt=0;
while(q.size()){
int k=q.front();q.pop();
ans[++cnt]=k;
for(int i=0;i<a[k].size();i++){
int tmp=a[k][i];
dep[tmp]=dep[k]+1;
mx=max(mx,dep[tmp]);
iin[tmp]--;
if(!iin[tmp]) q.push(tmp);
}
}
if(cnt==n&&mx==n) return 1;
else if(cnt<kk) return 2;
else return 0;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
cin>>s;
int x=s[0]-'A',y=s[2]-'A';
mark[x]=mark[y]=1;
a[x].push_back(y);in[y]++;
if(topo()==1){
printf("Sorted sequence determined after %d relations: ",i);
for(int j=1;j<=n;j++) printf("%c",ans[j]+'A');
printf(".");
return 0;
}
else if(topo()==2){
printf("Inconsistency found after %d relations.",i);
return 0;
}
}
printf("Sorted sequence cannot be determined.");
return 0;
}
0X03 P3243 [HNOI2015]菜肴制作
首先其限制关系符合一般拓扑排序的条件,但要求序号小的要尽量靠前。
这并不表示字典序越小越优。比如:限制为
但是,可以想到,靠后的位置数字越大一定越优。所以考虑反向建边,并用优先队列(大根堆)来维护最大值,然后答案倒序输出即可。
Code:
#include<bits/stdc++.h>
using namespace std;
int T,n,m,x,y,in[100005],ans[100005],cnt;
vector<int> a[100005];
priority_queue<int> q;
void solve(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++) a[i].clear(),in[i]=0;
while(q.size()) q.pop();
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
a[y].push_back(x),in[x]++;
}
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
cnt=0;
while(q.size()){
int k=q.top();q.pop();
ans[++cnt]=k;
for(int i=0;i<a[k].size();i++){
int tmp=a[k][i];
if(!(--in[tmp])) q.push(tmp);
}
}
if(cnt<n) printf("Impossible!\n");
else{
for(int i=n;i>=1;i--) printf("%d ",ans[i]);
printf("\n");
}
}
int main(){
scanf("%d",&T);
while(T--) solve();
return 0;
}
0X04 P4316 绿豆蛙的归宿
记
设
注意初始化
然后就是和一般拓扑一样的做法。
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,in[100005],out[100005],u,v;
double ans[100005],g[100005],w;
struct node{
int to;
double dis;
}k;
vector<node> a[100005];
queue<int> q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%lf",&u,&v,&w);
k.to=v,k.dis=w;
a[u].push_back(k);
in[v]++,out[u]++;
}
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
g[1]=1.0;
while(q.size()){
int tt=q.front();q.pop();
for(int i=0;i<a[tt].size();i++){
node tmp=a[tt][i];
ans[tmp.to]+=(ans[tt]+tmp.dis*g[tt])/(double)out[tt];
g[tmp.to]+=g[tt]/(double)out[tt];
if(!(--in[tmp.to])) q.push(tmp.to);
}
}
printf("%.2lf",ans[n]);
return 0;
}
0X05 CF977D Divide by three, multiply by two
先将所有数两两成对判断,如果
然后直接跑拓扑,找到合法拓扑序即可。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,in[105],x,y,b[105];
vector<int> a[105];
queue<int> q;
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) continue;
if(b[i]*2==b[j]) a[i].push_back(j),in[j]++;
else if(b[i]%3==0&&b[i]/3==b[j]){
a[i].push_back(j),in[j]++;
}
}
}
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
while(q.size()){
int k=q.front();q.pop();
printf("%lld ",b[k]);
for(int i=0;i<a[k].size();i++){
in[a[k][i]]--;
if(!in[a[k][i]]) q.push(a[k][i]);
}
}
return 0;
}
0X06 CF510C Fox And Names
考虑连边方式:
将相邻的字符串两两拿出来,从第一位开始逐位向后比较,相等就跳过,不相等就要记录。
因为按“字典序”从小到大排,所以从前一个的字符串的那一位字母向后一个字符串的那一个字母连边,然后就直接 break(因为已经满足了!)。
其实可以优化,记录下已经确定的边的关系,再碰到如果符合就可以直接跳过,不符合就直接无解,但以下程序中没有写
考虑一种特殊情况,后一个字符串是前一个的严格前缀,这样根据字典序的定义是前面的较大,但不会判断出来,所以直接标记一下无解即可。
最后就是常规的判环。
Code:
#include<bits/stdc++.h>
using namespace std;
int n,in[35],u[105],v[105],ans[105],cnt;
string s[105];
vector<int> a[35];
queue<int> q;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=2;i<=n;i++){
int l1=s[i-1].size(),l2=s[i].size();
for(int j=0;j<l1;j++) u[j+1]=s[i-1][j]-'a';
for(int j=0;j<l2;j++) v[j+1]=s[i][j]-'a';
bool fl=0;
for(int j=1;j<=min(l1,l2);j++){
if(u[j]==v[j]) continue;
fl=1;
a[u[j]].push_back(v[j]);
in[v[j]]++;
break;
}
if(!fl&&l1>l2){
printf("Impossible");
return 0;
}
}
for(int i=0;i<26;i++) if(!in[i]) q.push(i);
while(q.size()){
int k=q.front();q.pop();
ans[++cnt]=k;
for(int i=0;i<a[k].size();i++){
in[a[k][i]]--;
if(!in[a[k][i]]) q.push(a[k][i]);
}
}
if(cnt<26){
printf("Impossible");
return 0;
}
for(int i=1;i<=26;i++) printf("%c",ans[i]+'a');
return 0;
}
0X07 AT1726 Dictionary for Shiritori Game
初看就是普通拓扑,其实比较有技巧。
要判断先后手必胜情况比较麻烦。考虑以下几种情况(设
容易想到,没有出度的点
看
再看
从这张图总结出:
- 当一个点没有出度时,他就必败
- 当一个点通向的所有点必胜时,他就必败
- 当一个点通向的点中,有一个点必败,他就必胜
可以看到,以上一个点的胜败条件是由他通向的点决定的,所以考虑反向建边。
同时注意,拓扑时一个点的状态如果已经确定就不用再管了。
只有通向他的点中有必胜(已反向),那么他才减入度,直到减到
但是只要通向他的点中有必败,就可以直接判必胜了(上文已讲)。
Code:
#include<bits/stdc++.h>
using namespace std;
int n,m,in[100005],ans[100005],x,y; //1败 2胜
vector<int> a[100005];
queue<int> q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
a[y].push_back(x),in[x]++;
}
for(int i=1;i<=n;i++){
if(!in[i]) ans[i]=1,q.push(i);
}
while(q.size()){
int k=q.front();q.pop();
for(int i=0;i<a[k].size();i++){
int tmp=a[k][i];
if(ans[k]==2) in[tmp]--;
if(ans[tmp]!=0) continue;
if(ans[k]==1) ans[tmp]=2,q.push(tmp);
if(!in[tmp]) ans[tmp]=1,q.push(tmp);
}
}
if(ans[1]==1) printf("Sothe\n");
else if(ans[1]==2) printf("Snuke\n");
else printf("Draw\n");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话