新智认知杯:CSL的魔法(思维+简单图论)

题目描述:
有两个长度为 n 的序列,a0,a1,…,an−1和 b0,b1,…,bn−1。CSL 有一种魔法,每执行一次魔法,可以任意挑选一个序列并任意交换序列中两个元素的位置。CSL 使用若干次魔法,得到最终的序列 a 和 b,并且想要让 a0*b0+a1*b1+…+an−1*bn−1的值最小化。求解 CSL 至少使用多少次魔法,能够达到最小化的目标。

贪心:大配小,可以得到最小。要大配小,就要交换位置。
如何知道要换到哪个位置?我用的离散化,离散化成(1,2,…n)的一个序列,离散化后的b序列每个数字对应要配对的数字可以直接用 n-bi+1计算得出,用数组映射出a的每个元素应该放的位置。

已知每个元素应该放的位置,问题转化为如果用最小交换次数得到目标序列。看似是一个错排问题,首先想到一点:类似模拟的过程,每个点到它要到的位置是一定要和那个位置交换的,如果全部直接对对应位置交换,得到的就直接是最小交换次数。
问题是交换的话变换的是两个点的位置,状态变了,下次交换时就是不同的状态。如何处理呢?用dfs跟踪处理吗?代码能写出来吗?

再进一步思考,如果把每个位置的元素的位置和最终应该放到的位置连起来,可以发现什么?一定是一些不相交的连续的环?这样一来整个问题变得很清晰,首先每个元素都必有一个要去的位置,我们把他当成一条出边,那么每个元素最后总会进到一个环里,也就是说所有的需要交换的最后是一个个的环。

至此,这道题可能可以用图论来解。
那么,一个环要交换多少次能得到对应的结果?假设一个环有n条边,那么需要交换n-1次。简单的拿一个两条边的环验证一下就行了。

那么问题很简单了,求环,然后用把环的点的个数减1然后全部加起来。因为全部是单向边,当时想的就是强连通,然后计每个强连通分量的点的个数。实际上因为环不相交,全是分隔开的简单环,用dfs和直接用循环跳转复杂度相同,直接用循环常数非常小。

那么这题的做法就是:离散化,映射,求环(dfs或tarjan或循环)。
然而我在用tarjan的时候第一次统计答案方式错了,没有通过自己的一组样例。也许当初输错了吧。导致没有尽早A掉。
更换统计方式:发现每有一个环我们要扣掉一个点,总共只有n个点,那么有几个环我们就会扣掉几个点。因此直接用点数减去环数得到答案,交一发A掉这题

还是贴出我的tarjan吧。。。

#include
using namespace std;
int n;
const int maxn = 1e6+10;
int a[maxn],b[maxn];
int ta[maxn],tb[maxn];
int pos[maxn];
map<int,int> mpa,mpb;
vector<int> g[maxn];
int dfn[maxn],res,cnt,low[maxn],sta[maxn],belong[maxn],top,vis[maxn];
void tarjan(int s){
 	low[s]=dfn[s]=++cnt;
 	sta[++top] = s;
 	vis[s] = 1;
 	for(int i = 0; i < g[s].size(); i++){
 	 	int v = g[s][i];
  		if(!dfn[v]) tarjan(v);
  		if(vis[v])
  	 		low[s]=min(low[s],low[v]);
 	}	 
 	if(low[s]==dfn[s]){
  		++res;
  		do{
   			belong[sta[top]]=res;
   			vis[sta[top]] = 0;
  		}while(sta[top--]!=s); 
 	}
}
int main(){
 	scanf("%d",&n);
 	for(int i = 1; i <= n; i++){
  		scanf("%d",&a[i]);
  		ta[i] = a[i];
 	}
 	for(int i = 1; i <= n; i++) {
  		scanf("%d",&b[i]);
  		tb[i]=b[i];
 	}
 	sort(a+1,a+n+1);
 	for(int i = 1 ;i <= n; i++) mpa[a[i]] = i;  //离散化a 
 	for(int i = 1; i <= n; i++) ta[i]=mpa[ta[i]];
 	sort(b+1,b+1+n);
 	for(int i = 1 ;i <= n; i++) mpb[b[i]] = i;  //离散化b
 	for(int i = 1; i <= n; i++) tb[i]=mpb[tb[i]];
 	for(int i = 1; i <= n; i++) pos[n-tb[i]+1] = i; //映射离散化后的a数组每个元素应该放的位置 
 	for(int i = 1; i <= n; i++)
 		g[i].push_back(pos[ta[i]]);     //建图 
 	for(int i = 1; i <= n; i++)
 	 	if(!dfn[i]) tarjan(i);
 	cout<<n-res<<endl;
}

你可能感兴趣的:(算法,ACM)