루트 번호 복잡 도 데이터 구조 (1)

68362 단어
목차
  • 제목 출처
  • 기초 모 팀
  • P4462 [CQOI 2018] 이 또는 서열
  • 수모 대
  • P1903 [국가 소집 대] 색깔 / 유지 보수 대열
  • 스크롤 백 팀
  • P5906 【 템 플 릿 】 스크롤 백 모 팀 & 모 팀 삭제 하지 않 음
  • 제목 출처
    설명 하기 전에 먼저 문제 목록 BF 의 데이터 구조 문제 목록 - 선택 루트 데이터 구조
    기초 막 대
    모 팀 알고리즘 의 핵심 은 폭력 적 인 풀이 이다. 단지 많은 기 교 를 가지 고 있어 서 알고리즘 의 복잡 도 는 데이터 규모 1e5 정도 의 문 제 를 처리 할 때 상당 하 다.간단 한 문 제 를 예 로 들 면 길이 가 n n n 인 서열 을 제시 하고 m m 차 구간 문 의 를 첨부 하여 구간 내의 모든 숫자 출현 횟수 의 제곱 합 을 구한다.가장 간단 한 폭력 사 고 는 첫 번 째 로 성실 하 게 구간 의 왼쪽 끝 점 에서 오른쪽 끝 점 까지 구간 내 에서 발생 횟수 를 집계 하면 서 제곱 과 공식 n 2 - (n - 1) 2 = 2 n - 1 n ^ 2 - (n - 1) ^ 2 = 2n - 1 n2 - (n - 1) 2 = 2 n - 1 누적 구 해 를 묻 는 것 이다.이후 매번 질문 할 때마다 이전 질문 을 바탕 으로 구간 의 왼쪽 점 과 오른쪽 점 을 계속 이동 하면 된다.매번 문의 하 는 복잡 도 는 O (n) O (n) O (n) 로 모두 n n 회 문의 하 며, 총 시간 복잡 도 는 O (n 2) O (n ^ 2) O (n2) 로 5e 4 의 데 이 터 량 은 절대 시간 을 초과 합 니 다.
    모 팀 은 단지 이전의 해법 을 약간 수정 할 뿐이다.먼저 길이 n n n 의 서열 을 n \ sqrt {n} n 블록 으로 나 누고 각 조각의 길 이 는 n \ sqrt {n} n 이다. 그 다음 에 다음 과 같은 규칙 에 따라 문의 한 것 을 정렬 한다. 첫 번 째 키 워드 는 구간 왼쪽 끝 점 이 있 는 블록 으로 작은 것 부터 큰 것 까지 이다.두 번 째 키 워드 는 구간 오른쪽 단점 으로 작은 것 부터 큰 것 까지 누 릅 니 다.이 작업 의 시간 복잡 도 는 정렬 의 복잡 도 O (n l o g n) O (nlogn) O (nlogn) 이다.그 다음 에 이 새로운 질문 순서에 따라 폭력 적 인 사 고 를 실 행 했 는데 그 결과 시간 복잡 도 는 O (n) O (n \ sqrt {n}) O (N) 였 다.어떻게 이해 하 는 지 에 대해 서 는 정렬 후 물 어 볼 수 있 는 분포 연구.왼쪽 점 이 같은 블록 안에 있 는 질문 은 오른쪽 점 이 단 조 롭 게 증가 하기 때문에 오른쪽 점 은 항상 오른쪽으로 이동 합 니 다.왼쪽 점 이 같은 블록 안에 있 는 질문 에 대해 그들의 오른쪽 점 이동 은 모두 O (n) O (n) O (n) O (n) 이 고 모두 n \ sqrt {n} n 블록 이 있 기 때문에 오른쪽 점 이동 의 전체 시간 복잡 도 는 O (n) O (n \ sqrt {n}) O (n) 이다.왼쪽 점 을 보면 같은 블록 안의 왼쪽 점 의 이동 길 이 는 n \ sqrt {n} n 보다 작 을 것 이 고, 인접 블록 안의 이동 은 고작 2 n 2 \ sqrt {n} 2n 이다. 평균 적 으로 물 어 볼 때마다 왼쪽 점 을 n \ sqrt {n} n 개의 위치 로 이동 해 야 한다. 모두 n n 개의 문의, 즉 왼쪽 점 이동 의 전체 시간 복잡 도 는 O (n n) O (n \ sqrt {n}) O 이다.(N). 정렬, 좌우 점 이동 을 통합 하고 전체 시간 복잡 도 는 O (n) O (n \ sqrt {n}) O (N) 등급 입 니 다.
    P4462 [CQOI 2018] 이상 또는 시퀀스
    - 문제 설명 - 원제 참조 - 문제 풀이 사고 - 본 문 제 는 모 팀 이 해결 할 수 있 는 전형 적 인 데이터 구조 문제 입 니 다. 먼저 서열 의 접두사 가 다 르 거나 이 서열 을 s u m sum sum 이 라 고 부 릅 니 다. 우리 가 어느 구간 의 차이 점 이나 k k k k k k 를 알 고 싶 을 때 사실은 한 쌍 의 l l l 과 r r 만족, s u m l - 1 * 8853 ° s u m r = k sum {l - 1}\ \ oplus sum {r} = k suml - 1 ⊕ sumr = k, 이 또는 성질 에 따라 s u m l - 1 ⊕ k = s u m r sum {l - 1} \ \ oplus k = sum {r} suml - 1 ⊕ k = sumr 또는 s u m r ⊕ k = s u m l - 1 sum {r} \ \ oplus k = sum {l - 1}sumr ⊕ k = suml − 1. 따라서 이 또는 i 의 구간 개 수 를 기록 하 는 데 사용 할 배열 만 필요 합 니 다. 이후 정렬 된 질문 에서 구간 이 새로운 경계 로 확 대 될 때마다 이 또는 값 을 누적 합 니 다.\ \ oplus k sum 경 계 는 8853 ° k 의 구간 갯 수 입 니 다. 여기 서 모 팀 의 최적화 기 교 를 제시 합 니 다. 정렬 을 물 을 때 왼쪽 끝 점 이 있 는 블록 이 홀수 라면 오른쪽 끝 점 은 작은 것 에서 큰 것 으로 정렬 하고 짝수 라면 오른쪽 끝 점 은 큰 것 에서 작은 것 으로 배열 할 수 있 습 니 다. 이러한 장점 은 문의 왼쪽 끝 점 이 한 조각 에서 다른 조각 으로 갈 때 오른쪽 끝 점 입 니 다.블록 에서 가장 작은 오른쪽 점 으로 한꺼번에 되 돌아 가 는 것 이 아니 라 가 까 운 곳 으로 먼저 이동 할 수 있 습 니 다. 이론 적 으로 시간 과 비용 을 절약 할 수 있 습 니 다. 코드 -
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    const int MAXN = 1e5+5;
    int n;
    int m;
    int k;
    int ans[MAXN];
    int sum[MAXN];
    int cnt[4*MAXN]; 
    struct query {
    	int id;
    	int l;
    	int r;
    	int pos;
    } q[MAXN];
    
    bool cmp(query q1, query q2) {
    	return q1.pos==q2.pos ? (q1.pos&1 ? q1.r<q2.r : q1.r>q2.r) : q1.pos<q2.pos;
    }
    int main() {
    	scanf("%d %d %d", &n, &m, &k);
    	for(int i=1;i<=n;++i) {
    		int in;
    		scanf("%d", &in);
    		sum[i] = sum[i-1]^in;
    	}
    	int siz = (int)sqrt((double)n);
    	for(int i=1;i<=m;++i) {
    		scanf("%d %d", &q[i].l, &q[i].r);
    		q[i].id = i;
    		--q[i].l;
    		q[i].pos = (q[i].l - 1) / siz + 1;
    	}
    	sort(q+1,q+n+1,cmp); 
    	int left = 1;
    	int right = 0;
    	int now = 0;
    	for(int i=1;i<=m;++i) {
    		while(right<q[i].r) {
    			++right;
    			++cnt[sum[right]];
    			now += cnt[sum[right]^k];  
    		}
    		while(right>q[i].r) {
    			now -= cnt[sum[right]^k];
    			--cnt[sum[right]];
    			--right;
    		}
    		while(left<q[i].l) {
    			now -= cnt[sum[left]^k];
    			--cnt[sum[left]];
    			++left;
    		}
    		while(left>q[i].l) {
    			--left;
    			++cnt[sum[left]];
    			now += cnt[sum[left]^k];
    		}
    		ans[q[i].id] = now;
    	}
    	for(int i=1;i<=m;++i) {
    		printf("%d
    "
    , ans[i]); } return 0; }

    수모 대 를 거느리다
    말 그대로 수정 작업 을 지원 하 는 모 쌍 알고리즘 입 니 다. 일반적인 제목 에 포 함 된 설명 은 구간 의 한 점 값 에 대한 변 화 를 포함 합 니 다. 예 를 들 어 길이 n n n 의 서열 을 제시 하고 m m m 의 조작 이 있 습 니 다. 작업 은 두 가 지 를 포함 합 니 다. 하 나 는 해당 하 는 숫자 를 다른 숫자 로 바 꾸 는 것 입 니 다. 하 나 는 구간 [l, r] [l, r] [l, r] [l, r] 의 조회 입 니 다.
    띠 슈 모 팀 은 기초 모 팀 에 시간 축 을 추가 했다. 매번 수정 할 때마다 새로운 시간 점 으로 기록 하고 문의 하 는 정렬 방식: 첫 번 째 키 워드 는 왼쪽 끝 점 의 블록 으로 작은 것 에서 큰 것 으로, 두 번 째 키 워드 는 오른쪽 끝 점 의 블록 으로 작은 것 에서 큰 것 으로, 세 번 째 키 워드 는 수정 시간 으로 똑 같이 작은 것 에서 큰 것 으로 한다. 일반적으로 이런 문제 블록 의 크기 는 n 23 n 을 취 하 는 경향 이 있다.^ {\ frac {2} {3}} n32. 하나의 질문 에서 새로운 질문 으로 이동 하려 면 좌우 점 의 이동 뿐만 아니 라 시간의 흐름 도 고려 해 야 합 니 다. 현재 질문 의 시간 이 이전 질문 의 시간 보다 적 으 면 시간 순서에 따라 순 서 를 역 추적 하여 복원 해 야 합 니 다. 시간 복잡 도의 증명 과 기초 모 팀 은 대동소이 합 니 다. O (n 5 3) O (n ^ {\ frac {5} {3}) O 입 니 다.(n35​)。
    P1903 [국가 훈련 팀] 색상 / 유지 보수 대기 열
    - 문제 설명 - 하나의 서열 을 제시 하고 두 가지 조작 이 있 습 니 다. 1. 질문 구간 [l, r] [l, r] [l, r]중, 서로 다른 숫자의 개수, 2. 아래 표 가 x x x 인 숫자 를 다른 숫자 로 바 꿉 니 다. 문제 풀이 사고방식 - 하나의 수 조 를 만들어 구간 에 있 는 모든 숫자 가 나타 나 는 횟수 를 유지 합 니 다. 구간 이 확장 되 었 을 때, 현재 위치의 숫자 가 이전의 누적 횟수 가 0 이면 답 수 는 1 을 추가 합 니 다. 구간 이 축소 되 었 을 때, 현재 위치의 숫자 가 이전의 누적 횟수 에 있 으 면.숫자 가 1 이면 답 수 에 1 을 더 합 니 다. 좌우 점 이 다 이동 한 다음 에 시간 선 이동 을 고려 하여 시간 을 과거 나 미래 에서 현재 시간 으로 미 루 고 수 정 된 위치 가 질문 구간 에 있 는 지 를 고려 할 때마다 답 수 를 비슷 한 구간 확장 이나 축소 시 수정 합 니 다. 코드 -
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    const int MAXN = 1e6 + 6;
    int n;
    int m;
    int cnt[MAXN];
    int a[MAXN];
    int ans[MAXN];
    int cntq;
    int cntr;
    struct query {
    	int id;
    	int l;
    	int r;
    	int posl;
    	int posr;
    	int t;
    } q[MAXN];
    struct rswap {
    	int pre;
    	int x;
    	int col;
    } r[MAXN];
    
    bool cmp(query q1, query q2) {
    	return q1.posl!=q2.posl ? q1.posl<q2.posl : q1.posr!=q2.posr ? q1.posr<q2.posr : q1.t<q2.t;
    }
    int main() {
    	scanf("%d %d", &n, &m);
    	for(int i=1;i<=n;++i) {
    		scanf("%d", &a[i]);
    	}
    	int kuai = pow((double)n, (double)2/3);
    	for(int i=1;i<=m;++i) {
    		char c[2];
    		scanf(" %s", c);
    		if (c[0]=='Q') {
    			++cntq;
    			q[cntq].id = cntq;
    			scanf("%d %d", &q[cntq].l, &q[cntq].r);
    			q[cntq].posl = q[cntq].l / kuai;
    			q[cntq].posr = q[cntq].r / kuai;
    			q[cntq].t = cntr;  	
    		}
    		else {
    			++cntr;
    			scanf("%d %d", &r[cntr].x, &r[cntr].col);
    		}
    	}
    	sort(q+1,q+cntq+1,cmp);
    	int nowans = 0;
    	int nowt = 0;
    	int left = 1;
    	int right = 0;
    	for(int i=1;i<=cntq;++i) {
    		while(right<q[i].r) {
    			nowans += !cnt[a[++right]]++;
    		}
    		while(right>q[i].r) {
    			nowans -= !--cnt[a[right--]];
    		}
    		while(left<q[i].l) {
    			nowans -= !--cnt[a[left++]];
    		}
    		while(left>q[i].l) {
    			nowans += !cnt[a[--left]]++;
    		}
    		while(nowt<q[i].t) {
    			++nowt;
    			r[nowt].pre = a[r[nowt].x];
    			a[r[nowt].x] = r[nowt].col;
    			if(left<=r[nowt].x && r[nowt].x<=right) {
    				nowans += !cnt[a[r[nowt].x]]++;
    				nowans -= !--cnt[r[nowt].pre];
    			}
    		}
    		while(nowt>q[i].t) {
    			a[r[nowt].x] = r[nowt].pre;
    			if(left<=r[nowt].x && r[nowt].x<=right) {
    				nowans += !cnt[a[r[nowt].x]]++;
    				nowans -= !--cnt[r[nowt].col];
    			}
    			--nowt;
    		}
    		ans[q[i].id] = nowans;
    	}
    	for(int i=1;i<=cntq;++i) {
    		printf("%d
    "
    , ans[i]); } return 0; }

    스크롤 백 막 대
    스크롤 백 모 팀 이 해결 하 는 문 제 는 일반적으로 '지속 가능 화' 의 맛 이 묻 습 니 다. 구간 두 점 의 이동 은 누적 개수 의 문제 가 아니 라 확장 전에 기 록 된 역사 버 전 을 고려 해 야 합 니 다. 예 를 들 어 길이 n n n 의 순 서 를 제시 하고 m m m 의 질문 이 있 습 니 다. 문 제 는 구간 [l, r] [l, r] [l, r] 에서 같은 숫자 로 떨 어 진 가장 먼 거리 입 니 다.
    두 개의 배열 을 유지 하고 있 습 니 다. 하나의 기록 구간 에서 가장 왼쪽 에 있 는 위치, 다른 기록 이 가장 오른쪽 에 있 는 위 치 를 유지 하고 있 습 니 다. 만약 에 원래 구간 에 같은 수치 v a l val 이 세 개 포함 되 어 있다 면 구간 이 축소 되 고 두 개의 v a l val 만 남 았 을 때 세 개의 v a l val 의 중간 위 치 를 어떻게 알 수 있 습 니까? 직관 적 인 생각 은 하나의 대기 열 로 차원 을 유지 하 는 것 입 니 다.모든 v a l val 의 위 치 를 보호 합 니 다. 이렇게 하면 공간 적 인 비용 이 많이 증가 합 니 다.
    모 팀 알고리즘 을 잘 사용 하 는 블록 사 고 는 비교적 적은 공간 을 사용 하 는 동시에 시간 복잡 도 를 O (n) O (n \ sqrt {n}) O (N) 로 유지 할 수 있 습 니 다.단계. 정렬 과 시퀀스 블록 을 묻 는 규칙 은 기본 모 팀 과 같 습 니 다. 유일한 차이 점 은 구간 이동 입 니 다. 구간 의 좌우 점 이 같은 블록 에 있 으 면 직접 폭력 으로 답 을 스 캔 합 니 다. 블록 크기 는 n \ sqrt {n} n 이 므 로 시간 복잡 도 는 O (n) O (\ sqrt {n}) O (n) 입 니 다.구간 의 좌우 점 이 서로 다른 블록 에 있 으 면 왼쪽 점 이 같은 블록 에 있 는 모든 질문 에 대해 우 리 는 왼쪽 점 의 위 치 를 블록 의 오른쪽 경계 에 정 하고 왼쪽 점 이 오른쪽 경계 와 문의 하 는 왼쪽 점 사이 에서 계속 이동 하도록 한다. 오른쪽 점 의 이동 은 기초 모 팀 과 같다. 우 리 는 먼저 오른쪽 점 을 이동 하여 현재 의 답 을 기록 하 자.(이 답 은 왼쪽 점 이 오른쪽 경계 에 있 을 때의 상황 을 나타 낸다)그리고 왼쪽 끝 점 을 질문 의 목표 위치 로 이동 합 니 다. 이 때 전체 질문 의 답 을 얻 을 수 있 습 니 다. 마지막 으로 왼쪽 끝 점 을 블록의 경계 가 있 는 곳 으로 거 슬러 올 라 가 가장 오른쪽 에 나타 난 위치 가 왼쪽 끝 점 에 굴 러 간 데 이 터 를 지 워 야 합 니 다. 현재 기 록 된 가장 먼 거리 도 왼쪽 끝 점 에서 왼쪽으로 확장 하기 전의 크기 로 거 슬러 올 라 갑 니 다. 현재 왼쪽 끝 점 이 있 는 블록 이 이전 질문 과 다르다 면.왼쪽 끝 점 의 시작 위 치 를 현재 블록의 오른쪽 경계 로 다시 설정 하 는 동시에 두 개의 유지 보수 그룹 을 비 워 야 합 니 다. 이 작업 은 본질 적 으로 왼쪽 끝 점 의 왕복 스크롤 일 뿐 실제 시간 복잡 도 는 O (n) O (\ sqrt {n}) O (n) 입 니 다. 따라서 전체적으로 스크롤 모 팀 과 기초 모 팀 의 시간 복잡 도 는 상수 상의 차이 일 뿐 입 니 다.
    P5906 【 템 플 릿 】 막 대 를 굴 리 기 & 막 대 를 삭제 하지 않 기
    - 제목 설명 - 원 제 를 보 세 요. 문제 풀이 방향 - 위 글 - 코드 를 보 세 요.
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    typedef pair<int, int> pii;
    const int MAXN = 2e5+5;
    int n;
    int m;
    pii in[MAXN];
    int history[MAXN];
    int cntkuai;
    int cntl[MAXN];
    int cntr[MAXN];
    int a[MAXN];
    int ans[MAXN];
    int bru[MAXN];
    struct query {
    	int id;
    	int l;
    	int r;
    	int pos;
    } q[MAXN];
    
    bool cmp(query q1, query q2) {
    	return q1.pos!=q2.pos ? q1.pos<q2.pos : q1.r<q2.r;
    }
    int main() {
    	scanf("%d", &n);
    	for(int i=1;i<=n;++i) {
    		scanf("%d", &in[i].first);
    		in[i].second = i;
    	}
    	sort(in+1, in+n+1);
    	int prenum = 0;
    	int newnum = 0;
    	for(int i=1;i<=n;++i) {
    		if (in[i].first!=prenum) {
    			++newnum;
    			prenum = in[i].first;
    		}
    		a[in[i].second] = newnum;
    	}
    	int kuai = sqrt((double)n);
    	scanf("%d", &m);
    	for(int i=1;i<=m;++i) {
    		scanf("%d %d", &q[i].l, &q[i].r);
    		q[i].id = i;
    		q[i].pos = (q[i].l-1) / kuai + 1;
    	}
    	sort(q+1, q+m+1, cmp);
    	int nowkuai = 0;
    	int leftbase = 1;
    	int left = 1;
    	int right = 0;
    	int nowans = 0;
    	int clearptr = 0;
    	for(int i=1;i<=m;++i) {
    		if ((q[i].r-1)/kuai+1==q[i].pos) {
    			for(int j=q[i].l;j<=q[i].r;++j) {
    				bru[a[j]] = 0;
    			}
    			for(int j=q[i].l;j<=q[i].r;++j) {
    				if (!bru[a[j]]) {
    					bru[a[j]] = j;
    				}
    				else {
    					ans[q[i].id] = max(ans[q[i].id], j-bru[a[j]]);
    				}
    			}
    			continue;
    		}
    		if (nowkuai!=q[i].pos) {
    			for(int j=1;j<=clearptr;++j) {
    				cntr[history[j]] = 0;
    				cntl[history[j]] = 0;
    			}
    			nowkuai = q[i].pos;
    			leftbase = min(nowkuai*kuai, n);
    			left = leftbase + 1;
    			right = left - 1;
    			nowans = 0;
    			clearptr = 0;
    		}
    		while(right<q[i].r) {
    			++right;
    			cntr[a[right]] = right;
    			if (!cntl[a[right]]) {
    				cntl[a[right]] = right;
    				history[++clearptr] = a[right];
    			}
    			nowans = max(nowans, right-cntl[a[right]]);
    		}
    		int sw = nowans;
    		while(left>q[i].l) {
    			--left;
    			if (cntr[a[left]]) {
    				nowans = max(nowans, cntr[a[left]]-left);
    			}
    			else {
    				cntr[a[left]] = left;
    			}
    		}
    		ans[q[i].id] = nowans;
    		while(left<=leftbase) {
    			if (cntr[a[left]]==left) {
    				cntr[a[left]] = 0;
    			}
    			++left;
    		}
    		nowans = sw;
    	}
    	for(int i=1;i<=m;++i) {
    		printf("%d
    "
    , ans[i]); } return 0; }

    좋은 웹페이지 즐겨찾기