5.19~5.25
拓扑排序
拓扑排序不是对数字进行大小排序的那种排序,而是对一系列事物的顺序关系和依赖关系进行排序,属于图论。拓扑排序的排序结果通常不是唯一的。
一个图能进行拓扑排序的充要条件是它是一个**有向无环图(DAG) **。
拓扑排序需要用到点的入度(Indegree)和出度 (Outdegree)
- 入度:以点$ v
\(为终点的边的数量,称为\) v $的入度。 - 出度:以点$ u \(为起点的边的数量,称为\) u $的出度。
一个点的入度为0,说明它是起点,是排在最前面的;一个点的出度为0,说明它是终点,排在最后面。
拓扑排序的简单的图遍历,用BFS和DFS都能实现。
基于BFS的拓扑排序
(1)无前驱的顶点优先
先输出入度为0的点(无前驱,优先级最高),以下是具体步骤:
- 找所有入度为0的点,放入队列作为起点,这些点谁先谁后没有关系。如果找不到入度为0的点,说明这个图不是DAG,不存在拓扑排序。
- 弹出队首元素a,将a的所有邻居点入度-1,入度减为0的邻居点入队。没有减为0的不入队。
- 继续上述操作,直到队列为空为止。队列的输出就是一个拓扑排序。
拓扑排序无解的判断(找环):
如果队列已空,但是还有点未入队,那么这些点的入度都不为0,说明图不是DAG,不存在拓扑排序。这也说明图上存在环,可以用来找环,没有进入队列的点,就是环上的点。
(2)无后驱的顶点优先
和无前驱反过来就行,先找出度为0的点。
BFS的时间复杂度为$ O(n+m) $,其中m为入度为0的点。
基于DFS的拓扑排序
DFS很适合拓扑排序。
一个有向无环图,从一个入度为0的点$ u \(开始DFS,DFS递归返回的顺序就是拓扑排序(是一个**逆序**)。DFS递归返回的首先是最底层的点,它一定是出度为0的点;然后逐步回退,最后输出起点\) u $。
如果图是有环图,则不存在拓扑排序,那么在递归时,会出现回退边。在代码中这样发现回退边:记录每个点的状态,如果dfs递归到某个点时发现它仍在前面的递归中没有处理完毕,说明存在回退边,不存在拓扑排序。
例题POJ 1270
#include <bits/stdc++.h>
using namespace std;
#define check (s[i]>='a' && s[i]<='z')
const int maxn = 30;
int n,in[maxn],a[maxn],topo[maxn];
bool link[maxn][maxn],vis[maxn];
void dfs(int u,int cnt){
topo[cnt] = u;
if(cnt == n-1){
for(int i=0;i<n;i++) cout << char('a'+topo[i]) << ' ';
cout << '\n';
return;
}
vis[u] = 1;
for(int i=0;i<n;i++){
if(!vis[a[i]] && link[u][a[i]])
in[a[i]]--;
}
for(int i=0;i<n;i++){
if(!vis[a[i]] && !in[a[i]])
dfs(a[i],cnt+1);
}
for(int i=0;i<n;i++){
if(!vis[a[i]] && link[u][a[i]])
in[a[i]]++;
}
vis[u] = 0;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
string s;
while(getline(cin,s)){
memset(in,0,sizeof(in));
memset(link,0,sizeof(link));
memset(vis,0,sizeof(vis));
int len = s.size();
n = 0;
for(int i=0;i<len;i++)
if(check) a[n++] = s[i]-'a';
sort(a,a+n);
getline(cin,s);
len = s.size();
int f = 1,st,ed;
for(int i=0;i<len;i++){
if(f && check){
f = 0;
st = s[i] - 'a';
continue;
}
if(!f && check){
f = 1;
ed = s[i] - 'a';
link[st][ed] = 1;
in[ed]++;
continue;
}
}
for(int i=0;i<n;i++){
if(!in[a[i]])
dfs(a[i],0);
}
cout << '\n';
}
return 0;
}
洛谷P1113

这题难度不高,我dfs和bfs各写了一份
bfs
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+5;
int n,dp[maxn],in[maxn],a[maxn];
vector <int> e[maxn];
void bfs(){
queue <int> q;
for(int i=1;i<=n;i++){
if(!in[i]){
q.push(i);
dp[i] = a[i];
}
}
while(!q.empty()){
int b = q.front();
q.pop();
for(auto i:e[b]){
in[i]--;
if(!in[i]) q.push(i);
dp[i] = max(dp[i],dp[b]+a[i]);
}
}
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=0;i<n;i++){
int a1,len,c;
cin >> a1 >> len;
a[a1] = len;
do{
cin >> c;
e[a1].push_back(c);
in[c]++;
}while(c);
}
bfs();
int ans = 0;
for(int i=1;i<=n;i++){
ans = max(ans,dp[i]);
}
cout << ans << '\n';
return 0;
}
dfs
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+5;
int n,a[maxn],dp[maxn];
vector <int> e[maxn];
int dfs(int x){
if(dp[x]) return dp[x];
int add = 0;
for(int i=0;i<e[x].size();i++){
add = max(add,dfs(e[x][i]));
}
dp[x] += add + a[x];
return dp[x];
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n;
for(int i=0;i<n;i++){
int a1,len,c;
cin >> a1 >> len;
a[a1] = len;
do{
cin >> c;
e[c].push_back(a1);
}while(c);
}
int ans = 0;
for(int i=1;i<=n;i++){
ans = max(ans,dfs(i));
}
cout << ans << '\n';
return 0;
}
洛谷P1347


虽一眼拓扑排序,但是这题有点难写,被各种坑卡了一下午,考验代码细节。
选择BFS进行拓扑
#include <bits/stdc++.h>
using namespace std;
const int maxn = 605;
int ji = 1;
int n,m,in[30],ou[30],cnt = 0;
vector <int> e[maxn];
map <int,int> ma;
bool vis[30]={0},c2 = 0,c3 = 0;
int topo(){
int t = 0;
bool temp[30]={0};
queue <int> q;
memset(ou,0,sizeof(ou));
cnt = 0;
//cout << "times: " << ji << " in[C]: " << in[3] << '\n';ji++;
for(int i=1;i<=n;i++){
if(!in[i] && vis[i]){
//cout << "in[i] de i: " << i << " in[i]: " << in[i] << '\n';
q.push(i);
t++;
ou[cnt++] = i, temp[i] = 1;
//cout << "rukou:" << i << '\n';
}
}
if(t != 1 && t != 0) c3 = 1;
//cout << "t1: " << t << '\n';
if(q.empty()){
//cout << "111\n";
c2 = 1;
return 2;
}
while(!q.empty()){
int a = q.front();
q.pop();
t = 0;
for(auto i:e[a]){
//if(temp[i]) {cout<<"a:" << a << " i:" << i << " temp[i]:" << temp[i];c2 = 1; return 2;}
in[i]--;
ma[i]++;
if(!in[i]){
q.push(i);
t++;
ou[cnt++] = i, temp[i] = 1;
}
}
if(t != 1 && t != 0) c3 = 1;
//cout << "t2: " << t << '\n';
}
for(int i=1;i<=26;i++) if(in[i]) {c2 = 1; return 2;}
for(auto i:ma) in[i.first] += i.second;
ma.clear();
//cout << "times: " << ji++ << " c2: " << c2 << " c3: " << c3 << '\n';
if(cnt == n && !c2 && !c3) return 1;
return 0;
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for(int i=1;i<=m;i++){
string s; cin >> s;
int s1 = s[0]-'A'+1, s2 = s[2]-'A'+1;
vis[s1] = vis[s2] = 1;
in[s2]++;
e[s1].push_back(s2);
int pd = topo();
if(pd == 1){
cout << "Sorted sequence determined after " << i << " relations: ";
for(int i=0;i<cnt;i++) cout <<char(ou[i]+'A'-1);
cout << ".\n";
return 0;
}
if(pd == 2){
cout << "Inconsistency found after " << i << " relations.\n";
return 0;
}
}
cout << "Sorted sequence cannot be determined.\n";
return 0;
}
cf 1019 div.2
作为中国人,题面放中文
A

很明显,数组y可以用数组a的所有元素的最小公倍数_LCM_构造出来
而当a中有重复的元素时,就不能构造出y了
所以对a去重即可
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t; cin >> t;
while(t--){
bool vis[101] = {0};
int n; cin >> n;
int len = n;
for(int i=1;i<=n;i++){
int c; cin >> c;
if(!vis[c]) vis[c] = 1;
else len--;
}
cout << len << '\n';
}
return 0;
}
B

注意到(注意力惊人),反转一个字串,最多可以减少两次操作次数,
像“0101””101“这种,既有”01“,也有”10“,一个”1“两边都是0,那么0->1->0就要切换两次;一个”0“两边都是1,那么1->0->1同样要切换两次;
如果1作为开头,还要再切换一次
找规律可以发现,有三种结果
当切换次数>2时,一定可以触发减少2次的情况,所以结果为n+cnt-2
当切换次数==2时,可以触发减少1次的情况,所以结果为n+1
当切换次数<2时,要么全0,要么就减少不了,所以是n+cnt
#include <bits/stdc++.h>
using namespace std;
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t; cin >> t;
while(t--){
int n; cin >> n;
string s; cin >> s;
int cnt = 0;
if(s[0] == '1') cnt++;
for(int i=1;i<n;i++)
if(s[i] != s[i-1]) cnt++;
if(cnt > 2) cout << n+cnt-2 << '\n';
else if(cnt == 2) cout << n+1 << '\n';
else cout << n+cnt << '\n';
}
return 0;
}
C

思维题
把一个数列划分为三段,要求三段的每一段的中位数所构成的数列的中位数 <= k
中位数的取法是$ floor(\frac{m}{2}) $,m是数列长度
做一个条件前、后缀和,触发累加的条件是a[i]<=k
条件前缀和是pre[i],存放[1,i]内<=k的数字个数
条件后缀和是hou[i](英语不好不知道该用啥单词),存放[n-i+1,n]内<=k的数字个数
而切割数列的切割点有三种情况:
- 两个左切割点(由前缀和得出)
- 两个右切割点(由后缀和得出)
- 一左一右(要求:
l < r-1)
左切割点的得出方法是2*pre[i] >= i,右切割点的得出方法是2*hou[i] >= n-i+1
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int a[maxn],pre[maxn],hou[maxn];
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t; cin >> t;
while(t--){
memset(pre,0,sizeof(pre));
memset(hou,0,sizeof(hou));
int n,k,l=0,r=0; cin >> n >> k;
for(int i=1;i<=n;i++){
cin >> a[i];
pre[i] += pre[i-1] + (a[i] <= k);
}
for(int i=n;i>=1;i--) hou[i] += hou[i+1] + (a[i] <= k);
bool pd = 0;
for(int i=1;i<=n;i++){
if(l && 2*pre[i] >= i && pre[i] > 1) pd = 1;
if(!l && 2*pre[i] >= i) l = i;
}
for(int i=n;i>=1;i--){
if(r && 2*hou[i] >= n-i+1 && hou[i] > 1) pd = 1;
if(!r && 2*hou[i] >= n-i+1) r = i;
}
if(l && r && l < r-1) pd = 1;
cout << (pd ? "YES\n" : "NO\n");
//for(int i=0;i<R.size();i++) cout << " R[i]: "<< R[i];
//cout << '\n';
//for(int i=1;i<=n;i++) cout << hou[i] << ' ';
//cout << '\n';
//if(L.size() >= 2 || R.size() >= 2) cout << "YES\n";
}
return 0;
}
D

看着论文一样的题目,瓜坐了很久...
挺难的构造题
主要用双指针,分奇偶轮数来构造这个排列。
- 奇数轮次:删除非局部最小值。为了确保被删除的元素不是局部最小值,它们应该比相邻的元素大。因此这些元素的值应该较大或较小。然后这样操作:从左到右处理
<font style="color:rgb(0, 0, 0);">rou[i]</font>中索引小于<font style="color:rgb(0, 0, 0);">index</font>的元素,分配较大的值(<font style="color:rgb(0, 0, 0);">r</font>递减),再反转<font style="color:rgb(0, 0, 0);">rou[i]</font>,从右到左处理索引大于<font style="color:rgb(0, 0, 0);">index</font>的元素,分配较大的值。 - 偶数轮次:删除非局部最大值。为了确保被删除的元素不是局部最大值,它们应该比相邻的元素小。因此这些元素的值应该较小或较大。相应的就是:从左到右处理
<font style="color:rgb(0, 0, 0);">rou[i]</font>中索引小于<font style="color:rgb(0, 0, 0);">index</font>的元素,分配较小的值(<font style="color:rgb(0, 0, 0);">l</font>递增)。接着反转<font style="color:rgb(0, 0, 0);">v[i]</font>,从右到左处理索引大于<font style="color:rgb(0, 0, 0);">vv</font>的元素,分配较小的值。
两个指针l和r,初始化为1和n。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int t,n,a[maxn],ans[maxn];
vector <int> rou[maxn];
int times(int m){
int x = 1;
while(m){
x++;
m >>= 1;
}
return x;
}
void solve(){
memset(a,0,sizeof(a));
memset(ans,0,sizeof(ans));
int n,index; cin >> n;
for(int i=1;i<=n;i++){
cin >> a[i];
rou[i].clear();
}
for(int i=1;i<=n;i++){
if(a[i] == -1) index = i;
else rou[a[i]].push_back(i);
}
int k = times(n), l = 1, r = n;
for(int i=1;i<=k;i++){
if(i & 1){
for(auto j:rou[i]){
if(j < index) ans[j] = r--;
else break;
}
reverse(rou[i].begin(),rou[i].end());
for(auto j:rou[i]){
if(j > index) ans[j] = r--;
else break;
}
}
else{
for(auto j:rou[i]){
if(j < index) ans[j] = l++;
else break;
}
reverse(rou[i].begin(),rou[i].end());
for(auto j:rou[i]){
if(j > index) ans[j] = l++;
else break;
}
}
}
ans[index] = l;
for(int i=1;i<=n;i++) cout << ans[i] << ' ';
cout << '\n';
}
int main(void){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> t;
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号