• Codeforces 578E. Walking!(贪心+线段树)


    Codeforces 578E. Walking!

    题目大意

    • 给出一个长度为 N N N字符串 S S S,由’L’和‘R’组成,
    • 求一个 N N N的排列 P P P,使得 S P i ≠ S p i + 1 S_{P_i}≠S_{p_{i+1}} SPi=Spi+1,也就是说要满足选择的相邻两个都不相同,
    • 同时要最小化 p i > p i + 1 p_i>p_{i+1} pi>pi+1的个数,也就是使排列相邻的两个数前一个大于后一个的数量最少。
    • 数据保证有解。
    • N ≤ 1 ∗ 1 0 5 N≤1*10^5 N1105

    题解

    • 观察样例再自己手玩一下,很快就发现可以贪心,
    • 首先显然若’R’的数量多,则第一个从’R’开始,反之同理;若两种字符一样多,则从哪个开始都可以。
    • 先尽量选择左边的,然后向右继续选最靠近它的符合条件的,这样一直下去,直到最后面选不了了,再回到最左边一个符合条件的,以此类推不断往复,似乎就可以求出最小答案,而且特别显然,可以用线段树维护。
    • 但是发现有一种特殊情况,
    • 假如出现“RLLR”的情况,且当前在第二个’L’的位置,按照贪心我们会先向右找一个’R’,然后发现没有了,返回左边第一个’L’,然后它后面也没有‘R’了,又往左一次返回到第一个’R’,这样返回了两次,
    • 而如果先直接返回第一个’R’,接下来就可以一直顺着走完,那么只返回了一次,更优。
    • 所以需要判断一下,如果当前字符为’L’,它右边的第一个‘R’右边剩下的全是‘R’,且从左边起第一个剩下的也是‘R’,那么直接回到最左边的‘R’;如果当前为‘R’同理。
    • 这样答案是正确的。

    代码

    #include<cstdio> 
    #include<cstring>
    #include<algorithm>
    using namespace std;
    #define N 100010
    char st[N];
    int a[N], n;
    int f[2][N * 4], ans[2][N], bz[N], fa[N], s[2], nxt[N];
    void ins(int v, int l, int r, int x, int o, int c) {
    	if(l == r) f[o][v] = c;
    	else {
    		int mid = (l + r) / 2;
    		if(x <= mid) ins(v * 2, l, mid, x, o, c);
    		else ins(v * 2 + 1, mid + 1, r, x, o, c);
    		f[o][v] = min(f[o][v * 2], f[o][v * 2 + 1]);
    	}
    }
    int find(int v, int l, int r, int x, int y, int o) {
    	if(l == x && r == y) return f[o][v];
    	int mid = (l + r) / 2;
    	if(y <= mid) return find(v * 2, l, mid, x, y, o);
    	if(x > mid) return find(v * 2 + 1, mid + 1, r, x, y, o);
    	return min(find(v * 2, l, mid, x, mid, o), find(v * 2 + 1, mid + 1, r, mid + 1, y, o));
    }
    int get(int k) {
    	if(fa[k] == k) return k;
    	return fa[k] = get(fa[k]);
    }
    int ss = 0;
    int ct(int l, int o) {
    	if(l >= n) return n + 1;
    	int l1 = find(1, 1, n, l + 1, n, o);ss++;
    	while(bz[l1]) {
    		if(l1 == n) return n + 1;
    		int l2 = nxt[l1];
    		fa[l1] = get(l2);
    		l1 = fa[l2];
    	}
    	return l1;
    }
    void solve(int o) {
    	int i;
    	s[0] = s[1] = n + 1;
    	memset(bz, 0, sizeof(bz));
    	for(i = n; i; i--) {
    		nxt[i] = s[a[i]];
    		fa[i] = i;
    		s[a[i]] = i;
    		ins(1, 1, n, i, 0, s[0]);
    		ins(1, 1, n, i, 1, s[1]);
    	} 
    	fa[n + 1] = n + 1;
    	int l = 0, r = o; ans[o][0] = 0;
    	for(i = 1; i <= n; i++)	{
    		int tt = ct(0, r);
    		if(ct(ct(l, r), 1 - r) == n + 1 && tt < ct(0, 1 - r)) {
    			int l1 = tt;
    			if(l1 < l) ans[o][0]++;
    			l = ans[o][i] = l1;
    			bz[l] = 1;
    			r = 1 - r;
    			continue;
    		}
    		
    		int l1 = ct(l, r);
    		if(l1 == n + 1) {
    			ans[o][0]++;
    			l1 = ct(0, r);
    		}
    		l = ans[o][i] = l1;
    		bz[l] = 1;
    		r = 1 - r;
    	}
    }
    int main() {
    	scanf("%s", st + 1);
    	n = strlen(st + 1);
    	int t[2] = {0, 0}, i;
    	for(i = n; i; i--) {
    		a[i] = (st[i] == 'R');
    		t[a[i]]++;
    	}
    	ans[0][0] = ans[1][0] = n + 1;
    	if(t[1] >= t[0]) solve(1);
    	if(t[0] >= t[1]) solve(0);
    	int o;
    	if(ans[0][0] < ans[1][0]) o = 0; else o = 1;
    	printf("%d
    ", ans[o][0]);
    	for(i = 1; i <= n; i++) printf("%d ", ans[o][i]);
    	return 0;
    } 
    
    哈哈哈哈哈哈哈哈哈哈
  • 相关阅读:
    ECSHOP文章详情页的标题上加个链接
    点击复制代码到粘贴板代码
    ecshop商城用户名和邮箱都能登陆方法
    ECSHOP商品页发表评论时 取消EMAIL必填
    ECSHOP 模板结构说明
    ecshop文章分类页 显视当前文章分类名称及商品分类页显视当前分类名称
    ecshop商城用户名和邮箱都能登陆方法
    Ecshop品牌页如何自定义Title
    常见的颜色搭配、衣裤搭配指南
    ECSHOP首页显示积分商城里的商品
  • 原文地址:https://www.cnblogs.com/LZA119/p/13910029.html
Copyright © 2020-2023  润新知