zoukankan      html  css  js  c++  java
  • JZOJ 6652. 【2020.05.27省选模拟】序列(贪心+序列翻转)

    JZOJ 6652. 【2020.05.27省选模拟】序列

    题目大意

    • 问给出的 N N N M M M的排列,按从头到尾依次加到序列首或尾的规则,共同能得到的新排列的个数,并给出字典序最小的方案。
    • 询问有多组。
    • T ≤ 50 , N , M ≤ 1000 , ∑ m ≤ 5000 Tle50,N,Mle1000,sum mle5000 T50N,M1000m5000

    题解

    • 先加入队列的数位置不好确定,但最后加入的数一定只能再两端,不妨考虑从后往前推。
    • 这样一来每个时刻已经构成的排列是一段前缀和一段后缀,记录指针 L i , R i L_i,R_i Li,Ri表示每个排序前端和后端添加到了新排列的哪个位置,令新排列为 A A A,注意对每个旧排列而言,最终得到的新排列 A A A的唯一的。
    • 从后依次给每一个排列加入一个数 a a a(即从后往前一列一列地加),分四种情况:
    • 1、若 A [ L i ] A[L_i] A[Li] A [ R i ] A[R_i] A[Ri]均为 0 0 0,则加入 L i L_i Li并将 L i L_i Li右移,答案乘 2 2 2;(因为此时加入右边也可以,但只考虑一半的情况)
    • 2、若 A [ L i ] = a A[L_i]=a A[Li]=a A [ R i ] = a A[R_i]=a A[Ri]=a,则直接移动对应的指针;
    • 3、若 A [ L i ] = 0 A[L_i]=0 A[Li]=0 A [ R i ] = 0 A[R_i]=0 A[Ri]=0,则添加 a a a后移动对应的指针;
    • 4、否则一定不合法,答案为 0 0 0
    • 这样便能得到方案数。
    • 接着需要构造方案。
    • 每一列数加完后,若此刻所有 L i L_i Li相等且所有 R i R_i Ri相等,即构成了一段可以翻转的区间,把它们记录下来,这种翻转是用来调整使字典序最小的。
    • 最终可以得到一连串相互包含的这样的区间,从内往外枚举它们,时刻满足当前字典序最小,有两种情况:
    • 1、若区间 i i i与区间 i + 1 i+1 i+1右端点重合(左端点不可能重合,因为在上面的情况 1 1 1选择加入了左端点),当且仅当 A [ p [ i + 1 ] . l ] < A [ p [ i ] . l ] A[p[i+1].l]<A[p[i].l] A[p[i+1].l]<A[p[i].l]时,翻转区间 i + 1 i+1 i+1,再翻转区间 i i i,即把 A [ p [ i + 1 ] . l ] A[p[i+1].l] A[p[i+1].l]调整到当前最左边;
    • 2、否则若 i i i区间左端点大于右端点,则翻转区间 i + 1 i+1 i+1,则翻转区间 i i i,因为要把 i + 1 i+1 i+1更小的一侧专项右边,再随 i i i一起翻回来。

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define N 1010
    #define md 1000000007
    int a[N][N], A[N], L[N], R[N], st[N][2];
    int read() {
    	int s = 0;
    	char x = getchar();
    	while(x < '0' || x > '9') x = getchar();
    	while(x >= '0' && x <= '9') s = s * 10 + x - 48, x = getchar();
    	return s;
    }
    void turn(int x) {
    	int s = st[x][0] + st[x][1];
    	for(int i = st[x][0]; i < s - i; i++) swap(A[i], A[s - i]);
    }
    int main() {
    	int tn = read();
    	while(tn--) {
    		int n = read(), m = read(), i, j;
    		memset(A, 0, sizeof(A));
    		for(i = 1; i <= n; i++) {
    			for(j = 1; j <= m; j++) scanf("%d", &a[i][j]);
    			L[i] = 1, R[i] = m;
    		}
    		int tot = 1, s = 1;
    		st[1][0] = 1, st[1][1] = m;
    		for(j = m; j && s; j--) {
    			for(i = 1; i <= n; i++) {
    				if(!A[L[i]] && !A[R[i]]) s = s * ((L[i] != R[i]) + 1) % md, A[L[i]++] = a[i][j];
    				else if(A[L[i]] == a[i][j]) L[i]++;
    				else if(A[R[i]] == a[i][j]) R[i]--;
    				else if(A[L[i]] != a[i][j] && A[R[i]] == 0) A[R[i]--] = a[i][j];
    				else if(A[R[i]] != a[i][j] && A[L[i]] == 0) A[L[i]++] = a[i][j];
    				else {
    					s = 0;
    					break;
    				}
    			}
    			int ok = 1;
    			for(i = 2; i <= n && ok; i++) if((L[i] != L[i - 1] || R[i] != R[i - 1])) ok = 0;
    			if(ok) st[++tot][0] = L[1], st[tot][1] = R[1];
    		}
    		printf("%d
    ", s);
    		if(s) {
    			for(j = tot; j; j--) {
    				if(j < tot && st[j][1] == st[j + 1][1]) {
    					if(A[st[j][0]] > A[st[j + 1][0]]) turn(j + 1), turn(j);
    				}
    				else {
    					if(st[j][0] < st[j][1] && A[st[j][0]] > A[st[j][1]]) {
    						if(j < tot) turn(j + 1);
    						turn(j);
    					}	
    				}
    			}
    			for(i = 1; i <= m; i++) printf("%d ", A[i]);
    			puts("");
    		}
    	}
    	return 0;
    }
    

    自我小结

    • 这题细节较多,同一部分容易想到各种不同做法,但往往有许多存在漏洞,因此改题时耽误了不少时间。
    • 同时也容易陷入思维死循环,忽视最简单的做法,若能捋清思路,可以大大提高效率,减小时间浪费。
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    elk.postman_collection.json
    win10 env
    run fink local
    run kafka local
    打码util
    DataFormatVerifyUtil
    springboot logging.config=classpath:log4j2.xml -Dlogging.config=config/log4j2.xml
    server.port server.servlet.context-path resources freemarker mybatis mail
    maven-war-plugin
    html头部有一行信息导致所有的css和jss都是 https了
  • 原文地址:https://www.cnblogs.com/LZA119/p/14608430.html
Copyright © 2011-2022 走看看