• JZOJ 6997. 2021.03.06【2021省赛模拟】排列(最小树形图)


    JZOJ 6997. 2021.03.06【2021省赛模拟】排列

    题目大意

    • p p p为一个 1 1 1 n n n的排列,令 F ( p ) = ∑ i = 1 n min ⁡ j = 1 i a p i ⨁ b p j F(p)=sum_{i=1}^n min_{j=1}^i a_{p_i}igoplus b_{p_j} F(p)=i=1nminj=1iapibpj,求使 F ( p ) F(p) F(p)最小且字典序最小的 p p p
    • n ≤ 50 nle50 n50

    题解

    • 若选择 b y b_y by a x a_x ax构成一组贡献,则相当于从 y y y x x x连了一条边,即要求 y y y在排列中需要在 x x x的前面,把所有的 ( x , y ) (x,y) (x,y)对应的边都连出来,边权为 b y ⨁ a x b_yigoplus a_x byax,则题目可以转化为在这些边中选出一些边构成树形结构,其中边的方向都由父亲指向儿子,并最小化边权和。
    • 基本上就是求最小树形图,但同时要考虑排列字典序最小。可以依次枚举每个位置填什么数,并固定下来,用剩下的点求最小树形图。每次选择权值和最小的且最小的数填入当前位置。
    • 在实现上有细节需要注意:最小树形图中,可能会出现自己连向自己的情况,并且也是合法的;用剩下的点求最小树形图时,根节点不仅是当前固定下来的点,而是前面已经固定好的所有点合并作为根。

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define N 110
    int a[N], b[N], dis[N][N], ar[N], tot, n;
    int ch[N], st[N], fa[N], vi[N], e[N][N], sn[N][N];
    int solve(int x) {
    	int s = dis[fa[x]][x];
    	int ci[N];
    	ci[0] = 0;
    	for(int i = 1; i <= tot; i++) if(sn[x][i]) ci[sn[x][i]] = i, ci[0]++;
    	for(int i = 1; i <= ci[0]; i++) fa[ci[i]] = ci[i % ci[0] + 1], s += solve(ci[i]);
    	return s;
    }
    int count(int v0) {
    	int i, j, k, ok = 0, l = -1;
    	memset(sn, 0, sizeof(sn));
    	while(!ok) {
    		l++;
    		memset(fa, 0, sizeof(fa));
    		memset(vi, 0, sizeof(vi));
    		// 非根节点的贡献 
    		for(i = 1; i <= e[l][0]; i++) if(e[l][i] != v0) {
    			int mi = 1e9, p, x = e[l][i];
    			// 在集合内,由非根、非自己转移 
    			for(j = 1; j <= e[l][0]; j++) if(e[l][j] != x && dis[e[l][j]][x] < mi) mi = dis[e[l][j]][x], p = e[l][j];
    			fa[x] = p;
    			// 由自己、根或已确定的序列转移 
    			int mii = (x <= n ? dis[x][x] : 1e9); p = x;
    			for(j = 1; j <= ar[0]; j++) if(dis[ar[j]][x] < mii) mii = dis[ar[j]][x], p = ar[j];
    			if(mii <= mi) {
    				vi[x] = 1;
    				fa[x] = p;
    			}
    		}
    		// vi[] = 1 --> 挂入确定的点,不可能产生环。需计入下一层,可任意往后挂 
    		vi[v0] = 1; int id = 1; 
    		ok = 1;
    		for(i = 1; i <= e[l][0]; i++) if(vi[e[l][i]]) e[l + 1][++e[l + 1][0]] = e[l][i];
    		for(i = 1; i <= e[l][0]; i++) if(!vi[e[l][i]]) {
    			id++;
    			st[0] = 0;
    			int x = e[l][i];
    			while(!vi[x]) {
    				vi[x] = id;
    				st[++st[0]] = x;
    				x = fa[x];
    			}
    			if(vi[x] == id) {
    				//构成环,缩点 
    				ok = 0;
    				tot++;
    				for(j = 1; j <= tot; j++) dis[tot][j] = dis[j][tot] = 1e9;
    				for(j = 1; st[j] != x && j <= st[0]; j++);
    				int j0 = j;
    				for(j; j <= st[0]; j++) {
    					int x = st[j];
    					sn[tot][x] = j - j0 + 1;
    					for(k = 1; k < tot; k++) {
    						dis[tot][k] = min(dis[tot][k], dis[x][k]);
    						dis[k][tot] = min(dis[k][tot], dis[k][x] - dis[fa[x]][x]);
    					}
    				}
    				e[l + 1][++e[l + 1][0]] = tot;
    				for(j = 1; st[j] != x && j <= st[0]; j++) {
    					e[l + 1][++e[l + 1][0]] = st[j];
    				}
    			}
    			else {
    				//未构成环,加入下一层 
    				for(j = 1; j <= st[0]; j++) e[l + 1][++e[l + 1][0]] = st[j];
    			}
    		}
    	}
    	int s = 0;
    	for(i = 1; i <= e[l][0]; i++) {
    		int x = e[l][i];
    		s += solve(x); 
    	}
    	return s; 
    }
    int main() {
    	int i, j, k;
    	scanf("%d", &n);
    	for(i = 1; i <= n; i++) scanf("%d", &a[i]);
    	for(i = 1; i <= n; i++) scanf("%d", &b[i]);
    	for(i = 1; i <= n; i++)
    		for(j = 1; j <= n; j++) dis[i][j] = b[i] ^ a[j];
    	for(i = 1; i <= n; i++) {
    		int mi = 1e9, x;
    		ar[0]++;
    		for(j = 1; j <= n; j++) if(!ch[j]) {
    			memset(e, 0, sizeof(e));
    			for(k = 1; k <= n; k++) if(!ch[k]) e[0][++e[0][0]] = k;//未选加入集合 
    			tot = n;
    			ar[i] = j;	
    			int t = count(j);
    			// 当前根的贡献 
    			int mii = 1e9;
    			for(k = 1; k <= i; k++) mii = min(mii, dis[ar[k]][j]);
    			if(t + mii < mi) mi = t + mii, x = j;
    		}
    		ch[x] = 1;
    		ar[i] = x;
    		if(i == 1) printf("%d
    ", mi);
    	}
    	for(i = 1; i <= n; i++) printf("%d ", ar[i]);
    	return 0;
    }
    

    自我小结

    • 这题看似是最小树形图的模板题,其实却有很多与之不同且极其需要注意的地方,同时是第一次写最小树形图,思路和细节上都出现了许多问题。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    跟我一起学算法——贪心算法
    跟我一起学算法——最大流
    跟我一起学算法——红黑树
    设计模式总结
    敏捷开发模型
    TCP安全,SYN Flooding 和 nmap
    ICMP和重定向攻击
    IP安全,DDoS攻击、tearDrop攻击和微小IP碎片攻击
    Netfilter,获取http明文用户名和密码
    Linux常用网络命令
  • 原文地址:https://www.cnblogs.com/LZA119/p/14608428.html
Copyright © 2020-2023  润新知