1.3.1 拓扑排序
用于有向无环图,做一些跟点层数有关的事情。
Eg1.神经网络
先把u[i]预处理好,重点要好好读题
#include<bits/stdc++.h>
using namespace std;
const int maxn = 110;
int n, p, c[maxn], u[maxn], tot, st[maxn], ind[maxn], out[maxn];
bool vis[maxn];
queue<int> q;
struct node{
int v, w, nxt;
} edge[maxn*maxn];
inline void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void TopSort(){
for(int i = 1; i <= n; i++)
if(!ind[i]){
q.push(i);
}
while(!q.empty()){
int now = q.front(); q.pop();
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
ind[to]--;
if(c[now] > 0) c[to] += edge[i].w * c[now];
if(!ind[to]){
q.push(to);
}
}
}
}
int main(){
scanf("%d%d", &n, &p);
for(int i = 1; i <= n; i++)
scanf("%d%d", &c[i], &u[i]);
for(int i = 1; i <= n; i++)
if(!c[i]) c[i] -= u[i];
for(int i = 1, x, y, z; i <= p; i++){
scanf("%d%d%d", &x, &y, &z);
ind[y]++;
out[x]++;
in(x, y, z);
}
TopSort();
bool flag = 0;
for(int i = 1; i <= n; i++)
if(!out[i] && c[i] > 0){
flag = 1;
printf("%d %d\n", i, c[i]);
}
if(!flag) printf("NULL\n");
return 0;
}
Eg2.车站分级
很有意思的题目。容易看出两个停靠车站之间的车站的等级都是严格小于停靠车站的,因此可以看作是一个有向无环图,求拓扑排序的级数即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1001;
queue<int> q;
int n, m, tot, final;
int ans[maxn], rudu[maxn], a[maxn], st[maxn];
bool v[maxn], mp[maxn][maxn];
struct node{
int v, nxt;
} edge[maxn*maxn];
inline void init(){
for(int i = 1; i <= n; i++)
v[i] = 0;
}
inline void in(int x, int y){
edge[++tot].v = y;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void topsort(){
for(int i = 1; i <= n; i++)
if(!rudu[i]) q.push(i);
while(!q.empty()){
int now = q.front(); q.pop();
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
rudu[to]--;
if(!rudu[to]){
q.push(to);
ans[to] = ans[now] + 1;
final = max(final, ans[to]);
}
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1, si, x; i <= m; i++){
init();
scanf("%d", &si);
for(int j = 1; j <= si; j++){
scanf("%d", &a[j]);
v[a[j]] = 1;
}
for(int j = a[1]; j <= a[si]; j++)
if(!v[j])
for(int k = 1; k <= si; k++)
if(!mp[a[k]][j]){
mp[a[k]][j] = 1;
in(a[k], j);
rudu[j]++;
}
}
topsort();
printf("%d\n", final+1);
return 0;
}
1.3.2 二分图染色
用来表示点与点之间的二元关系,非常好懂好写。
Eg1.封锁阳光大学
暴力染色计数即可
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10010;
int n, m, tot = 0;
int cnt[3], st[maxn], color[maxn];
struct node{
int v, nxt;
} edge[200010];
inline void in(int x, int y){
edge[++tot].v = y;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void dfs(int x, int co){
if(color[x]) return;
color[x] = co;
cnt[co]++;
for(int i = st[x]; i; i = edge[i].nxt){
int to = edge[i].v;
if(color[to] == co){
printf("Impossible\n");
exit(0);
}
if(!color[to])
dfs(to, 3-co);
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1, x, y; i <= m; i++){
scanf("%d%d", &x, &y);
in(x, y);
in(y, x);
}
int ans = 0;
for(int i = 1; i <= n; i++)
if(!color[i]){
cnt[1] = cnt[2] = 0;
dfs(i, 1);
ans += min(cnt[1], cnt[2]);
}
for(int i = 1; i <= n; i++)
cnt[color[i]]++;
printf("%d\n", ans);
return 0;
}
Eg2.双栈排序
隐密的图论。。由于不能存在i < j < k,a[j] > a[i] > a[i]。所以要不符合关系的点对不能扔一起,因此用到了二分图染色,如果染色冲突则不能双栈排序。然而我贪心贪错了。。。这份代码只有30pts。可以借鉴的好思路是如果只有一个栈该如何排序。
#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1010;
int n, tot, mp[maxn][maxn], s1[maxn], s2[maxn], a[maxn], col[maxn], f[maxn];
int st[maxn], top1, top2;
struct node{
int v, nxt;
} edge[maxn*maxn];
inline void in(int x, int y){
edge[++tot].v = y;
edge[tot].nxt = st[x];
st[x] = tot;
}
void DFS(int now){
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
if(col[to] == col[now]){
printf("0\n");
exit(0);
}
if(col[to]) continue;
col[to] = 3 - col[now];
DFS(to);
}
}
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
f[n+1] = inf;
for(int i = n; i; i--)
f[i] = min(f[i+1], a[i]);
for(int i = 1; i < n; i++)
for(int j = i+1; j <= n; j++)
if(a[i] > f[j+1] && a[j] > a[i]){
mp[i][j] = mp[j][i] = 1;
in(i, j);
in(j, i);
}
for(int i = 1; i <= n; i++)
if(!col[i]){
col[i] = 1;
DFS(i);
}
for(int i = 1; i <= n; i++){
if(!top1){
s1[++top1] = i;
printf("a ");
continue;
}
if(col[i] == 2){
if(!top2){
s2[++top2] = i;
printf("c ");
continue;
}
if(a[s2[top2]] >= a[i]){
s2[++top2] = i;
printf("c ");
}
if(a[s2[top2]] < a[i] && top2){
while(a[s2[top2]] < a[i] && top2){
top2--;
printf("d ");
}
s2[++top2] = i;
printf("c ");
}
}
else if(a[s1[top1]] >= a[i]){
s1[++top1] = i;
printf("a ");
}
else if(a[s1[top1]] < a[i] && top1){
while(a[s1[top1]] < a[i] && top1){
top1--;
printf("b ");
}
s1[++top1] = i;
printf("a ");
}
}
while(top1){
top1--;
printf("b ");
}
while(top2){
top2--;
printf("d ");
}
printf("\n");
return 0;
}
1.3.3 LCA
如果碰上LCA,那基本就是NOIp最难的题了。。。这玩意儿还能结合模拟的优化,结合树上差分,甚至结合一些我不会的东西,哪怕是贪心也很难受。。。这玩意儿可以通过倍增和Tarjan求,可我只会倍增。
Eg1.运输计划
二分+树上差分+LCA,要深刻理解树上差分的思想,因为x点经过cnt次,所以其来路必经cnt次,因为LCA处已-2.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 300010;
int n, m, st[maxn], fa[maxn][22], lca[maxn], len[maxn], sum[maxn], p[maxn], from[maxn], dep[maxn];
int tot, s[maxn], t[maxn], maxw, maxl;
struct node{
int v, w, nxt;
} edge[maxn*2];
inline void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void change(int num){
p[s[num]] += 1;
p[t[num]] += 1;
p[lca[num]] -= 2;
}
void BuildT(int now){
for(int i = 1; i <= 20; i++){
if(dep[now] < (1<<i)) break;
fa[now][i] = fa[fa[now][i-1]][i-1];
}
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
if(to == fa[now][0]) continue;
fa[to][0] = now;
sum[to] = sum[now] + edge[i].w;
dep[to] = dep[now] + 1;
from[to] = i;
BuildT(to);
}
}
int getLCA(int x, int y){
if(dep[x] < dep[y]) swap(x, y);
int delta = dep[x] - dep[y];
for(int i = 0; i <= 20; i++)
if(delta & (1 << i)) x = fa[x][i];
for(int i = 20; i >= 0; i--)
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
if(x == y) return x;
return fa[x][0];
}
inline int getMax(int now, int cnt){
int exc = p[now];
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
if(to == fa[now][0]) continue;
exc += getMax(to, cnt);
}
if(exc == cnt) maxw = max(maxw, edge[from[now]].w);
return exc;
}
inline bool check(int now){
maxw = 0;
memset(p, 0, sizeof(p));
int cnt = 0;
for(int i = 1; i <= m; i++)
if(len[i] > now){
cnt++;
change(i);
}
if(!cnt) return 1;
maxw = 0;
getMax(1, cnt);
return maxl - maxw <= now;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1, x, y, z; i < n; i++){
scanf("%d%d%d", &x, &y, &z);
in(x, y, z);
in(y, x, z);
}
BuildT(1);
for(int i = 1; i <= m; i++){
scanf("%d%d", &s[i], &t[i]);
lca[i] = getLCA(s[i], t[i]);
len[i] = sum[s[i]] + sum[t[i]] - (sum[lca[i]] << 1);
maxl = max(len[i], maxl);
}
int l = 0, r = maxl + 1, mid;
while(l < r){
mid = l+r >> 1;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", r);
return 0;
}
Eg2.货车运输
在最大生成树上跑LCA,预处理点到LCA的最大权值
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10010;
const int maxm = 50010;
const int inf = 0x3f3f3f3f;
int n, m, q, st[maxn], tot, cnt, f[maxn], s, t, fa[maxn][20], g[maxn][20], dep[maxn];
bool vis[maxn];
struct node{
int x, y, w;
} a[maxm];
struct data{
int v, w, nxt;
} edge[2*maxm];
inline bool cmp(node p, node q){
return p.w > q.w;
}
int find(int x){
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
inline void getInit(){
for(int i = 1; i <= n; i++) f[i] = i;
}
inline void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].nxt = st[x];
st[x] = tot;
}
inline void Kruskal(){
getInit();
for(int i = 1; i <= m; i++){
int fx = find(a[i].x);
int fy = find(a[i].y);
if(fx != fy){
f[fx] = fy;
cnt++;
in(a[i].x, a[i].y, a[i].w);
in(a[i].y, a[i].x, a[i].w);
}
}
}
void Tree(int now){
vis[now] = 1;
for(int i = 1; i <= 16; i++){
if(dep[now] < (1 << i)) break;
fa[now][i] = fa[fa[now][i-1]][i-1];
g[now][i] = min(g[now][i-1], g[fa[now][i-1]][i-1]);
}
for(int i = st[now]; i; i = edge[i].nxt){
int to = edge[i].v;
if(vis[to]) continue;
dep[to] = dep[now] + 1;
fa[to][0] = now;
g[to][0] = edge[i].w;
Tree(to);
}
}
int getLCA(int s, int t){
if(dep[s] < dep[t]) swap(s, t);
int delta = dep[s] - dep[t];
for(int i = 0; i <= 16; i++)
if(delta & (1<<i)) s = fa[s][i];
for(int i = 16; i >= 0; i--)
if(fa[s][i] != fa[t][i]){
s = fa[s][i];
t = fa[t][i];
}
if(s == t) return s;
return fa[s][0];
}
int ask(int later, int former){
int minw = inf;
int delta = dep[later] - dep[former];
for(int i = 0; i <= 16; i++)
if(delta & (1 << i)){
minw = min(g[later][i], minw);
later = fa[later][i];
}
return minw;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].w);
sort(a+1, a+m+1, cmp);
Kruskal();
for(int i = 1; i <= n; i++)
if(!vis[i]) Tree(i);
scanf("%d", &q);
while(q--){
scanf("%d%d", &s, &t);
if(find(s) != find(t)){
printf("-1\n");
continue;
}
int lca = getLCA(s, t);
printf("%d\n", min(ask(s, lca), ask(t, lca)));
}
return 0;
}
Eg4.开车旅行
不会,再说吧。
Eg5.跑路
倍增的友好的题目,先用倍增预处理,再跑一遍Floyd。
#include<bits/stdc++.h>
using namespace std;
int dis[60][60],n,m;
bool G[60][60][65];
void init()
{
memset(G,false,sizeof(G));
memset(dis,10,sizeof(dis));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
dis[x][y]=1;
G[x][y][0]=true;
}
}
void work()
{
for(int k=1;k<=64;k++)
for(int i=1;i<=n;i++)
for(int t=1;t<=n;t++)
for(int j=1;j<=n;j++)
if(G[i][t][k-1]&&G[t][j][k-1])
{
G[i][j][k]=true;
dis[i][j]=1;
}
}
void floyd()
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
int main()
{
init();
work();
floyd();
printf("%d",dis[1][n]);
return 0;
}