CF1783F 题解
以下是自己的题解以及自己的做题经过。其中读者的重点在于第二、四部分,第一、三部分可凭个人喜好已确定是否阅读。
思路历程
下面是自己没做出来的原因。
-
贪心
似乎感觉没法贪,且感觉反悔贪心也没有策略
-
按一定顺序模拟
因为考虑按一定顺序归位,比如说先把 $a,b$ 的第一位都通过操作变为 $1$ ,但是考虑可能后面的什么通过交换归位了再和前面的换就一举两得,所以可能由于当前。
-
动态规划
$dp[i]$ 表示把前 $i$ 个数归位的最小答案,但有可能有 $2$ 中说的情况,所以没法转移。
题解思路
首先我们简化问题:假设只要一个数组。
我们把 $i$ 向 $a_i$ 连边,这样得到了一个由环组成的有向图。
那么思考,如果我们要每一步都会使得一个点从环中删除,以连向自己。那么最后环中剩下的点就不用操作了。所以一个数组的操作次数是 $n$ 减环的个数。
接下来考虑两个是数组。因为每一步操作都可以使两个数组中的 $i$ 位置变成指定数值。那么当两个环剩下的元素相同那么是可以省掉操作对的这 $1$ 次的。
所以我们把每个环看成一个点,拥有相同颜色的环连边。由于每个环中只有剩下的一个点是可能省掉 $1$ 步的,所以我们跑二分图的最大匹配。而匹配出来的就是可以省掉的次数。所以答案就是 $n$ 减最大匹配数。
至于输出方案,我们要知道:有了我们的模型转换之后不管什么顺序都没有问题。所以我们只需要枚举每个 $i$ (因为前面的连边方案是把拥有编号相同的两个点的环所代表的点连边),看看这条边是否被匹配就行了,如果匹配了,那么就证明这个点没有被选并且操作过,不输出;否则输出 $i$ 。
时间复杂度 $O(n^2)$ ,在于求解二分图的最大匹配的匈牙利算法。
分析原因
以下是自己分析的没做出来的原因。
-
没想出建图方式
缺乏这样套路性的思维。在一个排列中 $i$ 向 $a_i$ 或者 $a_i$ 向 $i$ 连边的这种建图方式非常常见。
-
知道了建图方式之后没有想到性质
像这道题的一个性质需要简化问题,从只有一个数组开始。然后根据题目的两个数组选的都是 $i$ 这点去确定建图方式,而最后的得出是二分图的结论就比较显而易见。
代码
#include <bits/stdc++.h>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 3010;
int n, cnt, tota, totb, p[N], vis[N];
int a[N], b[N], t[N], ca[N], cb[N], mp[N][N];
vector<int> g[N];
void Get(int u, int col, int e[], int c[]){//获取每个点所处环的标号
c[u] = col;
if(!c[e[u]]) Get(e[u], col, e, c);
}
bool Dfs(int u){//二分图的最大匹配
for(int v: g[u]){
if(!vis[v]){
vis[v] = 1;
if(!p[v] || Dfs(p[v])){
p[v] = u;
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d", &n);
L(i, 1, n) scanf("%d", &a[i]);
L(i, 1, n) scanf("%d", &b[i]);
L(i, 1, n){
if(!ca[i]) Get(i, ++tota, a, ca);
if(!cb[i]) Get(i, ++totb, b, cb);
}
L(i, 1, n)
if(!mp[ca[i]][cb[i]])
mp[ca[i]][cb[i]] = i, g[ca[i]].push_back(cb[i]);//拥有相同节点的环连边
L(i, 1, tota){
L(j, 1, totb) vis[j] = 0;
cnt += Dfs(i);
}
printf("%d\n", n - cnt);
L(i, 1, totb)
if(p[i]) t[mp[p[i]][i]] = 1;
L(i, 1, n)
if(!t[i]) printf("%d ", i);
return 0;
}