NFLS 图论题单笔记(完结)

John的农场是一张 N*N 的方格图,贝茜住在左上角(1,1),John住在右下角(N,N)。

现在贝茜要去拜访John,每次都只能往四周与之相邻的方格走,并且每走一步消耗时间 T。

同时贝茜每走三步就要停下来在当前方格吃草,在每个方格吃草的用时是固定的,为 H[i][j]。

John想知道贝茜最少要多久才能到达John的家。

跑一遍djstl。

#include<bits/stdc++.h>
using namespace std;
constexpr int inf = 1e9;
int n, t, h[105][105],d[105][105][5];
int dx[] = {1,-1,0,0,},dy[] = {0,0,1,-1};

struct Pr{
	int a, b;
};
queue<Pr> q,q1;
int main(){
	cin>>n>>t;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= n;j++){
			cin>>h[i][j];
		}
	for(int i = 0;i < 105;i++){
		for(int j = 0;j < 105;j++){
			d[i][j][0] = d[i][j][1] = d[i][j][2] = d[i][j][3] = d[i][j][4] = inf;
		}
	}
	q.push({1,1}),q1.push({0,0}),d[1][1][0] = 0;
	while(!q.empty()){
		for(int i = 0;i < 4;i++){
			int x = q.front().a + dx[i],y = q.front().b + dy[i];
			if(x > 0 && x <= n && y > 0 && y <= n){
				int s = (q1.front().a + 1) % 3,v = q1.front().b
				+ t + (!s) * h[x][y];
				if(v < d[x][y][s]){
					q.push({x,y}),q1.push({s,v}),d[x][y][s] = v;	
				}
			}
		}
		q.pop(), q1.pop();
	}
	cout<<min(d[n][n][0],min(d[n][n][1],d[n][n][2]));
}

给定一个完全图,已知两点之间的距离,以及通过某个点的时间(起点和终点不需要花费时间)。你需要回答一些询问,输出两点之间的最短路径,同时需要输出方案。如果有多个方案,输出字典序最小的方案。

数据范围极小,djstl,同时统计一下路径。



一个n点m边无向图,边权均为1,有k个询问

每次询问给出(s,t,d),要求回答是否存在一条从s到t的路径,长度为d

路径不必是简单路(可以自交)

数据保证不存在自环

由于两个点路径距离为d,就一定存在距离为d+2的路径。所以分层图对每个点跑一边spfa求出每个点到其他点经过奇数路径和偶数路径的距离,然后就可以直接判了。

#include<bits/stdc++.h>
#define N 5005
#define INF 0x3f3f3f3f
using namespace std;
template<typename T>inline void read(T &a){
	char c=getchar();T x=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	a=f*x;
}
vector<int> t[N];
int n,m,k,tot,h[N];
bool vis[N],ans[1000005];
int dis[2][N];
struct query{
	int u,v,w;
}q[1000005];
struct node{
	int nex,to;
}edge[N<<1];
inline void add(int u,int v){
	edge[++tot].nex=h[u];
	edge[tot].to=v;
	h[u]=tot;
}
inline void spfa(int s){
	queue<int> Q;
	for(int i=1;i<=n;i++)dis[0][i]=INF,dis[1][i]=INF,vis[i]=0;
	Q.push(s);vis[s]=1;dis[0][s]=0;
	while(!Q.empty()){
		   int x=Q.front();Q.pop();vis[x]=0;
		for(int i=h[x];i;i=edge[i].nex){
			   int xx=edge[i].to,flg=0;
			if(dis[0][x]!=INF){
				if(dis[1][xx]>dis[0][x]+1)
					dis[1][xx]=dis[0][x]+1,flg=1;
			}
			if(dis[1][x]!=INF){
				if(dis[0][xx]>dis[1][x]+1)
					dis[0][xx]=dis[1][x]+1,flg=1;
			}
			if(flg&&!vis[xx])Q.push(xx),vis[xx]=1;
		}
	}
}
int main(){
	cin>>n>>m>>k;
	for(int i=1,u,v;i<=m;i++){
		read(u);read(v);
		add(u,v);add(v,u);
	}
	for(int i=1;i<=k;i++){
		read(q[i].u),read(q[i].v),read(q[i].w);
		t[q[i].u].push_back(i);
	}
	for(int i=1;i<=n;i++){
		if(!t[i].size())continue;
		spfa(i);
		for(int j=0;j<(int)t[i].size();j++){
			   int o=t[i][j];
			   int num=q[o].w%2;
			if(q[o].w>=dis[num][q[o].v]&&h[i])ans[o]=1;
		}
	}
	for(int i=1;i<=k;i++)
		if(ans[i])printf("TAK\n");
	else printf("NIE\n");
	return 0;
}

D.公路修建问题

OI island是一个非常漂亮的岛屿,自开发以来,到这儿来旅游的人很多。然而,由于该岛屿刚刚开发不久,所以那 里的交通情况还是很糟糕。所以,OIER Association组织成立了,旨在建立OI island的交通系统。 OI island有n 个旅游景点,不妨将它们从1到n标号。现在,OIER Association需要修公路将这些景点连接起来。一条公路连接两 个景点。公路有,不妨称它们为一级公路和二级公路。一级公路上的车速快,但是修路的花费要大一些。 OIER As sociation打算修n-1条公路将这些景点连接起来(使得任意两个景点之间都会有一条路径)。为了保证公路系统的 效率, OIER Association希望在这n-1条公路之中,至少有k条(0≤k≤n-1)一级公路。OIER Association也不希望为 一条公路花费的钱。所以,他们希望在满足上述条件的情况下,花费最多的一条公路的花费尽可能的少。而你的任 务就是,在给定一些可能修建的公路的情况下,选择n-1条公路,满足上面的条件。

输入格式

第一行有三个数n(1≤n≤10000),k(0≤k≤n-1),m(n-1≤m≤20000),这些数之间用空格分开。N和k如前所述,m表示有m对景点之间可以修公路。

以下的m-1行,每一行有4个正整数a,b,c1,c2(1≤a,b≤n,a≠b,1≤c2≤c1≤30000)

表示在景点a与b之间可以修公路,如果修一级公路,则需要c1的花费,如果修二级公路,则需要c2的花费。



形式化:一张图的边有两种边权,求一棵最小生成树使得至少包含k条第一种边。发现形式化题意看完了也就做完了。

显然出题人不希望我们这样做,如果这道题二级公路可以比一级公路贵这种方法就没法做了,所以可以二分答案,然后每次二分往生成树里放k条一级公路。

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5,M=2e4+5;
int fa[N];
struct type {
	int u,v,w,t;
	int index,choo;
}a[M],b[M];
vector<type> e;
int find(int x){
	if(x==fa[x]) return x;
	else return fa[x]=find(fa[x]);
}
void unite(int x,int y){
	fa[find(x)]=find(y);
}
bool cmp(type a,type b){
	return a.w<b.w;
}
bool cmp2(type a,type b){
	return a.t<b.t;
}
bool cmp3(type a,type b){
	return a.index<b.index;
}
int n,k,m,ta,tb;
int tc1,tc2,m1,ans;
int main()
{
	
	cin>>n>>k>>m;
	for(int i=1;i<=m-1;i++){
		cin>>ta>>tb>>tc1>>tc2;
		a[++m1].u=ta,a[m1].v=tb;
		a[m1].w=tc2,a[m1].t=tc1,a[m1].index=i;
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	sort(a+1,a+m1+1,cmp2);
	
	int i;
	for(i=1;i<=m1&&k;i++){
		if(find(a[i].u)!=find(a[i].v)){
			unite(a[i].u,a[i].v);
			ans=max(ans,a[i].t);
			k--;
			a[i].choo=1;
			e.push_back(a[i]);
		}
	}
	sort(a+i,a+m1+1,cmp);
	
	for(;i<=m1;i++){
		if(find(a[i].u)!=find(a[i].v)){
			unite(a[i].u,a[i].v);
			ans=max(ans,a[i].w);
			a[i].choo=2;
			e.push_back(a[i]);
		}
	}
	cout<<ans<<endl;
	//sort(e.begin(),e.end(),cmp3);
	//for(int i=0;i < (int)e.size();i++)
	//	cout<<e[i].index<<' '<<e[i].choo<<endl;
	
	return 0;
}



吃草

为了庆祝奶牛Bessie的生日,Farmer John给了她一块最好的牧场,让她自由的享用。

牧场上一共有N块草地(1≤N≤1000),编号为1...N,每块草地上牧草的质量都不同。

如果Bessie吃掉的草地上牧草质量为Q,她可以获得Q单位的能量。

每块草地最多和10块草地有相连的道路,在相连的两个草地之间走动需要消耗E单位的能量(1≤E≤1,000,000)。

Bessie可以从任意一块草地开始吃草,并且想要在获得了最多能量的时候停止。

有点遗憾的,Bessie是一头挑食的奶牛,一旦她吃过了一定质量的牧草,她就不会再吃相同或更低质量的牧草!但是她仍然很愿意路过某些草地,而不吃它们。实际上,她发现路过一块高质量的草地而不吃它,等一下返回再去享用,有时会更有利!

请帮忙计算Bessie能够获得的能量的最大值。

发现往返两点之间的最短代价就是最小路径,而最优解的构造方法是从小到大把每块草地的质量排序,然后挨个吃。有的草地吃了它可能会亏,所以就把他踢出去。这样就可以构造一个dp。Fi表示吃第i块草的最大收益。Fi = max(Fj - DISij + VALj)

#include<bits/stdc++.h>
using namespace std;
constexpr int maxn = 2010;
int n, E;
struct edge{
	int to,next,val;
}e[maxn * 10];
int head[maxn];
int cnt;
void addedge(int u,int v){
	e[++cnt].to = v;
	e[cnt].val = E;
	e[cnt].next = head[u];
	head[u] = cnt;
}
int dis[maxn][maxn];
bool vis[maxn];
struct node{
	int dis,pos;
	bool operator<(const node x) const{
		return dis > x.dis;
	}
};
constexpr int inf = 1e9;
void djstl(int S){
	priority_queue<node> q;
	for(int i = 0;i < maxn;i++) dis[S][i] = inf,vis[i] = false;
	q.push((node){0,S});
	dis[S][S] = 0;
	while(!q.empty()){
		node temp = q.top();
		q.pop();
		int x = temp.pos;
		if(vis[x]) continue;
		vis[x] = true;
		for(int i = head[x];i;i = e[i].next){
			int y = e[i].to;
			if(dis[S][x] + e[i].val < dis[S][y]){
				dis[S][y] = dis[S][x] + e[i].val;
				if(!vis[y]){
					q.push((node){dis[S][y],y});
				}
			}
		}
	}
}

void getdis(){
	for(int i = 1;i <= n;i++){
		djstl(i);
	}
}

int dp[maxn];
struct grass{
	int val,cur;
	bool operator <(grass x) const{
		return val < x.val;
	}
}grass[maxn];

int main(){
	cin>>n>>E;
	for(int i = 1;i <= n;i++){
		int q,d;
		cin>>q>>d;
		grass[i].val = q;
		grass[i].cur = i;
		for(int j = 1;j <= d;j++){
			int v;
			cin>>v;
			addedge(i,v);
		}
	}
	getdis();
	

	
	for(int i=0;i<=n;++i)
		dis[0][i]=0;
	
	sort(grass+1,grass+n+1);
	for(int i = 1;i <= n;i++) dp[i] = grass[i].val;
	int ans = 0;
	
	
	for(int i = 1;i <= n;i++){
		for(int j = 1;j < i;j++){
			dp[i] = max(dp[i],dp[j] - dis[grass[j].cur][grass[i].cur]+grass[i].val);
		}
		ans = max(ans,dp[i]);
	}
	cout<<ans<<endl;
	return 0;
}


最优贸易

image
tarjan缩点,缩完之后从出发点求一遍前缀最大价格,从目的地往前求一遍后缀最小价格。枚举每个节点两个值一减就是答案。

记忆宫殿

image

初始入度为\(0\)的点为源点。

我们令 \(S\) 为如果成立,就能够推出事件的源点集合。

当事件成立时,显然 \(S\) 中的点必有至少一个是真的。所以我们只要把所有包含 \(S\) 的事件都标记为真就行了。

Pro-Professor Szu

某大学校内有一栋主楼,还有 栋住宅楼。这些楼之间由一些单向道路连接,但是任意两栋楼之间可能有多条道路,也可能存在起点和终点为同一栋楼的环路。存在住宅楼无法到达主楼的情况。

现在有一位古怪的教授,他希望每天去主楼上班的路线不同。

一条上班路线中,每栋楼都可以访问任意多次。我们称两条上班路线是不同的,当且仅当两条路线中存在一条路是不同的(两栋楼之间的多条道路被视为是不同的道路)。

现在教授希望知道,从哪些住宅楼前往主楼的上班路线数最多。

到达n+1后,可以选择停下或者继续走



首先发现若一个大小大于 1 的 SCC 或自环(下称为不合法结构)能够到达教学楼,则该不合法结构内部每个点到教学楼的路径数量都是无穷大。

显然可以先缩点,然后拓扑排序。

先将反图上入度为 0 的非教学楼点入队跑一遍拓扑排序。注意此时不合法结构可以入队,因为它们没有到达教学楼的路径。
最后,若出现没有入队的点,说明这个点能够到达一个不合法结构,因此路径数同样为无穷大。此外,若 \(f_i>36500\) 也不符合题意。

自己的代码没调出来,一直不过hack,贴一个题解的,过了就补(已)

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, ed, ban[N], deg[N], f[N];
int dn, dfn[N], low[N], cn, col[N], top, stc[N], vis[N];
struct linklist {
  int cnt, hd[N], nxt[N], to[N];
  void add(int u, int v) {nxt[++cnt] = hd[u], hd[u] = cnt, to[cnt] = v;}
} e, g;
void tarjan(int id) {
  dfn[id] = low[id] = ++dn, stc[++top] = id, vis[id] = 1; // 0 -> 1
  for(int _ = e.hd[id]; _; _ = e.nxt[_]) {
    int it = e.to[_];
    if(!dfn[it]) tarjan(it), low[id] = min(low[id], low[it]);
    else if(vis[it]) low[id] = min(low[id], dfn[it]);
  }
  if(low[id] == dfn[id]) {
    col[id] = ++cn, ban[cn] = stc[top] != id;
    while(stc[top] != id) col[stc[top]] = cn, vis[stc[top--]] = 0; // id -> cn
    vis[id] = 0, top--;
  }
}
int main() {
#ifdef ALEX_WEI
  freopen("1.in", "r", stdin);
  freopen("1.out", "w", stdout);
#endif
  cin >> n >> m;
  for(int i = 1; i <= m; i++) {
    int u, v;
    scanf("%d%d", &u, &v);
    e.add(u, v);
  }
  for(int i = 1; i <= n + 1; i++) if(!dfn[i]) tarjan(i);
  for(int i = 1; i <= n + 1; i++)
    for(int _ = e.hd[i]; _; _ = e.nxt[_]) {
      int it = e.to[_];
      if(i == it) ban[col[i]] = 1;
      else if(col[i] != col[it]) g.add(col[it], col[i]), deg[col[i]]++;
    }
  ed = col[n + 1];
  queue<int> q;
  for(int i = 1; i <= cn; i++) if(i != ed && !deg[i]) q.push(i);
  memset(vis, 0, sizeof(vis));
  while(!q.empty()) {
    int t = q.front();
    q.pop(), vis[t] = 1;
    for(int _ = g.hd[t]; _; _ = g.nxt[_]) {
      int it = g.to[_];
      if(!--deg[it] && it != ed) q.push(it);
    }
  }
  if(!ban[ed]) assert(!deg[ed]), q.push(ed), f[ed] = 1;
  while(!q.empty()) {
    int t = q.front();
    q.pop(), vis[t] = 1;
    for(int _ = g.hd[t]; _; _ = g.nxt[_]) {
      int it = g.to[_];
      if(ban[it]) continue;
      f[it] = min(36501, f[it] + f[t]);
      if(!--deg[it]) q.push(it);
    }
  }
  vector<int> ans;
  for(int i = 1; i <= n; i++)
    if(!vis[col[i]] || f[col[i]] == 36501)
      ans.push_back(i);
  if(!ans.empty()) puts("zawsze");
  else {
    int mx = 0;
    for(int i = 1; i <= n; i++) {
      if(f[col[i]] > mx) mx = f[col[i]], ans.clear();
      if(f[col[i]] == mx) ans.push_back(i);
    }
    cout << mx << "\n";
  }
  cout << ans.size() << "\n";
  for(int it : ans) cout << it << " ";
  return cerr << "Time: " << clock() << endl, 0;
}
posted @ 2024-11-18 21:46  Dreamers_Seve  阅读(1)  评论(0编辑  收藏  举报