基环树
- 基环树,顾名思义就是基于一个环的树,有n个点和n条边。只有一个环的图,其实并不是树,而是一种特殊的图。
- 基环树分类:
<1>内向树:每个点出度为1;
<2>外向树:每个点入度为1;
<3>无向树:建的是无向边; - 基环树解题的思路:找到环,然后断环成树。
例题:
- 洛谷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;
}
- 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;
}
- 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.思路分析:显而易见,如果某个动物最开始没有动物害怕他的话,那就可以直接卖掉了,把能直接卖掉的直接卖掉之后,就去找每个环。最后卖掉的肯定就是环里面价值最小的那个动物,因为除了价值最小的那个动物,别的动物都能以双倍 的价钱卖出去,然后注意判断多棵基环树,不要只判断一次。这道题还是比较简单的。
- 代码:
点击查看代码
#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;
}