基环树

  • 基环树,顾名思义就是基于一个环的树,有n个点和n条边。只有一个环的图,其实并不是树,而是一种特殊的图。
  • 基环树分类:
    <1>内向树:每个点出度为1;
    <2>外向树:每个点入度为1;
    <3>无向树:建的是无向边;
  • 基环树解题的思路:找到环,然后断环成树。
    例题:
  1. 洛谷p2607 骑士
    <1>题目大意:有n个骑士,每个骑士的战斗力为w,每个骑士都有一个讨厌的骑士,现在要选一些骑士出征打仗,问最大能选择的战斗力为多少。若a讨厌b,那么a和b不能一起出征。
    <2>思路分析:这题目是不是有点像《没有上司的舞会》?那么就做类似的思考,因为如果这是一棵树的话就好办了,可是题目给的是个图,还是有环的图。我们发现这是一颗基环树,所以找到环的两端的l和r点然后断开就可以了。然后分别对以l和r为根的子树进行dp取两者的最大值就可以了。那么是不是可能会有疑问:为什么这样两者取最大值就包含了全部情况了呢?因为分别以l和r做根就是不考虑l和不考虑r的情况,而两者取最大值就相当于是包含了全集。至于为什么要找这个l和r,因为我们要断环成树啊,不然每个骑士都出不去。
    <3>代码:写的时候注意要开longlong,wa了好几发了;
点击查看代码
#include<bits/stdc++.h>                      
using namespace std;
const int N = 1e6 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
ll w[N],vis[N];
vector<ll> a[N];
ll l,r;
ll dp[N][2];
void find(ll u,ll fa) {
	vis[u] = 1;
	for(auto i : a[u]) {
		if(i == fa) {
			l = u;
			r = fa;
			return;
		}
		if(vis[i]) continue;
		find(i,fa);
	}
}
ll dfs(ll u,ll root) {
	dp[u][1] = w[u],dp[u][0] = 0;
	for(auto v : a[u]) {
		if(v == root) continue;
		dfs(v,root);
		dp[u][0] += max(dp[v][1],dp[v][0]);
		dp[u][1] += dp[v][0];
	}
	return dp[u][0];
}
void solved() {
	int n;
	cin >> n;
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		int u;
		cin >> w[i] >> u;
		a[u].push_back(i);
	}
	for(int i = 1; i <= n; i++) {
		if(!vis[i]) {
			l = r = 0;
			find(i,i);
			if(r) {
				ll sum1 = dfs(l,l);
				ll sum2 = dfs(r,r);
				ans += max(sum1,sum2);
			}
		}
	}
	cout << ans << "\n";
}
int main() {
	int t = 1;
	//cin >> t;
	while(t--) {
		solved();
	}
	return 0;
}
  1. CFS1867D Cyclic Operations
    <1>题目大意:
    Egor 有一个长度为 \(n\) 的数组 \(a\) ,最初由零组成。然而,他想把它变成另一个长度为 \(n\) 的数组 \(b\)
    由于埃戈尔不走简单的路径,所以只能使用下面的操作(可能是零次,也可能是多次):
  • 选择长度为 \(k\) 的数组 \(l\) ( \(1 \leq l_i \leq n\) ,所有 \(l_i\) 都是不同的),并将每个元素 \(a_{l_i}\) 改为 \(l_{(i\%k)+1}\) 。( \(1 \leq i \leq k\) ).
    他开始关注是否可以只通过这些操作得到数组 \(b\) 。由于 Egor 还是个程序员初学者,他请求您帮助他解决这个问题。
    操作 \(\%\) 意味着取余数,也就是说, \(a\%b\) 等于数字 \(a\) 除以数字 \(b\) 的余数。

    <2>思路分析:我们发现,如果某个位置的值和它的下标不一样,假设下标是a,值是b。那么我们一定可以构造类似[a,b......]的l数组去改变a位置的值,所以我们可以压根不用管b位置变成什么样,也就是说,a变成什么样是和b有关的,所以考虑构造一个有向图,边由a指向b。所以,不在环上的点,怎么构造都行,但是在环上的点就需要等于环长度的数组来构造了。所以环的长度必须为k。还有就是特判一下k为1的时候,下标必须等于值的大小,否则若是下标等于值的大小但是k不为1是构造不出来的。
    <3>PS:重新写的时候还是出了点小问题,本来想着用拓扑排序把除了环以外点先去了,然后后来我也不知道为什么自己否定自己了。。。可能没想清楚吧。然后就是这可能是一颗基环树森林,所以不能只考虑一个根,重写的时候给我wa麻了。

点击查看代码
#include<bits/stdc++.h>                      
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int a[N],vis[N];
vector<int> g[N];
int res;
void dfs(int u,int fa) {
	vis[u] = 1;
	res++;
	for(auto v : g[u]) {
		if(v == fa) return;
		if(vis[v]) continue;
		dfs(v,fa);
	}
}
void solved() {
	int n,k;
	cin >> n >> k;
	bool flag = 1;
	vector<int> d(n + 1);
	for(int i = 1; i <= n; i++) {
		vis[i] = 0;
		g[i].clear();
		cin >> a[i];
		g[i].push_back(a[i]);
		d[a[i]]++;
	}
	if(k == 1) {
		for(int i = 1; i <= n; i++) {
			if(a[i] != i) flag = 0;
		}
		if(flag) cout << "YES\n";
		else cout << "NO\n";
		return;
	} else {
		for(int i = 1; i <= n; i++) {
			if(a[i] == i) flag = 0;
		}
		if(!flag) {
			cout << "NO\n";
			return;
		}
	}
	queue<int> q;
	for(int i = 1; i <= n; i++) {
		if(!d[i]) q.push(i);
	}
	while(!q.empty()) {
		auto t = q.front();q.pop();
		vis[t] = 1;
		if(--d[g[t][0]] == 0) q.push(g[t][0]);
	}
	for(int i = 1; i <= n; i++) {
		if(!vis[i]) {
			res = 0;
			dfs(i,i);
			if(res != k) {
				cout << "NO\n";
				return;
			}
		}
	}
	cout << "YES\n";
}
int main() {
	int t = 1;
	cin >> t;
	while(t--) {
		solved();
	}
	return 0;
}
  1. F. Selling a Menagerie 1872F
    1.题目大意:
    您是动物园的主人,动物园里有 \(n\) 只动物,编号从 \(1\)\(n\) 。然而,动物园的维护费用相当昂贵,因此您决定出售动物园!
    众所周知,每只动物都害怕另外一只动物。更确切地说,动物 \(i\) 害怕动物 \(a_i\)\(a_i \neq i\) )。另外,每只动物的成本也是已知的,动物 \(i\) 的成本等于 \(c_i\)
    您将按照固定的顺序出售所有动物。从形式上看,您需要选择某个排列顺序 \(^\dagger\) \(p_1, p_2, \ldots, p_n\) ,然后出售动物 \(c_i\)\(p_1, p_2, \ldots, p_n\) ,先出售动物 \(p_1\) ,然后出售动物 \(p_2\) ,以此类推,最后出售动物 \(p_n\)
    当你卖出动物 \(i\) 时,有两种可能的结果:
  • 如果动物 \(a_i\) 在动物 \(i\) 之前卖出,那么卖出动物 \(i\) 后你会得到 \(c_i\) 元。

  • 如果动物 \(a_i\) 在动物 \(i\) 之前没有出售,那么出售动物 \(i\) 会得到 \(2 \cdot c_i\) 元。(令人惊讶的是,目前害怕的动物更值钱)。
    你的任务是选择出售动物的顺序,使总利润最大化。
    例如,如果 \(a = [3, 4, 4, 1, 3]\)\(c = [3, 4, 5, 6, 7]\) ,而你选择的排列顺序是 \([4, 2, 5, 1, 3]\) ,那么:

  • 第一只出售的动物是 \(4\) 。动物 \(a_4 = 1\) 之前没有出售过,因此出售它可以获得 \(2 \cdot c_4 = 12\) 元。

  • 第二只要出售的动物是动物 \(2\) 。动物 \(a_2 = 4\) 之前被出售过,因此出售它你将获得 \(c_2 = 4\) 元。

  • 第三只要出售的动物是动物 \(5\) 。动物 \(a_5 = 3\) 之前没有卖出,所以卖出它你会得到 \(2 \cdot c_5 = 14\) 元。

  • 第四只要出售的动物是动物 \(1\) 。动物 \(a_1 = 3\) 之前没有出售过,所以出售它你会得到 \(2 \cdot c_1 = 6\) 元。

  • 第五只要出售的动物是动物 \(3\) 。动物 \(a_3 = 4\) 之前已经出售,因此出售该动物你将获得 \(c_3 = 5\) 金钱。
    在这种排列方式下,你的总利润为 \(12 + 4 + 14 + 6 + 5 = 41\) 。注意, \(41\) 并不是本例中可能的最大利润。
    \(^\dagger\) 长度为 \(n\) 的排列是由 \(n\) 个不同的整数组成的数组,这些整数从 \(1\)\(n\) ,顺序不限。例如, \([2,3,1,5,4]\) 是一个排列数组,但 \([1,2,2]\) 不是一个排列数组( \(2\) 在数组中出现了两次), \([1,3,4]\) 也不是一个排列数组( \(n=3\) ,但 \(4\) 出现在数组中)。

    2.思路分析:显而易见,如果某个动物最开始没有动物害怕他的话,那就可以直接卖掉了,把能直接卖掉的直接卖掉之后,就去找每个环。最后卖掉的肯定就是环里面价值最小的那个动物,因为除了价值最小的那个动物,别的动物都能以双倍 的价钱卖出去,然后注意判断多棵基环树,不要只判断一次。这道题还是比较简单的。

    1. 代码:
点击查看代码
#include<bits/stdc++.h>                      
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int g[N],c[N];
int vis[N];
void solved() {
    int n;
    cin >> n;
    vector<int> ans;
    vector<int> d(n + 1);
    for(int i = 1; i <= n; i++) {
        cin >> g[i];
        d[g[i]]++;
        vis[i] = 0;
    }
    for(int i = 1; i <= n; i++) cin >> c[i];
    queue<int> q;
    for(int i = 1; i <= n; i++) {
        if(!d[i]) {
            //cout << i << "\n";
            q.push(i);
        }
    }
    while(!q.empty()) {
        
        auto t = q.front();q.pop();
        //cout << t << endl;
        vis[t] = 1;
        ans.push_back(t);
        if(--d[g[t]] == 0) q.push(g[t]);
    }
    int fst,minn = 1e8;
    for(int i = 1; i <= n; i++) {
        if(!vis[i]) {
            int root = g[i];
            minn = c[i];
            fst = i;
            vis[i] = 1;
            while(root != i) {
                vis[root] = 1;
                if(c[root] < minn) {
                    fst = root;
                    minn = c[root];
                }
                root = g[root];
            }
            int nxt = g[fst];
            while(nxt != fst) ans.push_back(nxt),nxt = g[nxt];
            ans.push_back(fst);
        }
    }
    //cout << fst << "\n";

    for(auto i : ans) {
        cout << i << " ";
    }
    cout << "\n";
}
int main() {
	int t = 1;
	cin >> t;
	while(t--) {
		solved();
	}
	return 0;
}
posted @ 2023-11-16 18:18  orzkeyhacker  阅读(77)  评论(0编辑  收藏  举报