• 从今以后


    题意:

    给出一个长度为n(<=1e5)的序列,判断这个序列否合法,它任意区间总有一个元素只在这个区间出现一次。

    题解:

    方法一:

    对于n^2的暴力是很好想的,枚举一个起点i,一直到j,维护一个类似于前缀和的东西,表示i到j中有几个数只出现了一次,如果为0那么就不合法。

    因为j 的枚举是从i ~ n的,所以很容易就优化下去了。

    那么现在已经算出了1~i中出现一次的数个数,现在考虑将最前面的数删去,那么会对后面造成影响,怎么在第一次的基础上得到而不是重新计算呢?

    设last[i]表示xi[i]这个数后面第一个相同数字的位置,设最前面的数的编号为i,那么对于[i + 1,last[i] - 1]这个区间应该减1,对于[last[i],last[last[i]] - 1]这个区间加1,后面的区间不变。

    那么只需要维护一个最小值就好了,如果这个最小值为0,那么就不合法了。

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 2e5 + 7;
    int mini[N << 2], lazy[N << 2];
    int kase, n, xi[N], disc[N], dcnt, ans[N], cnt[N], id[N], last[N];
    
    #define mid (l + r >> 1)
    #define lson o << 1, l, mid
    #define rson o << 1 | 1, mid + 1, r
    #define clr(a, b) memset (a, b, sizeof a)
    void pushdown (int o)
    {
    	if (!lazy[o]) return;
    	mini[o << 1]  += lazy[o], lazy[o << 1] += lazy[o];
    	mini[o << 1 | 1] += lazy[o], lazy[o << 1 | 1] += lazy[o];
    	lazy[o] = 0;
    }
    
    int query (int o, int l, int r, int L, int R)
    {
    	if (l == L && r == R) return mini[o];
    	pushdown(o);
    	if (R <= mid) return query (lson, L, R);
    	else if (L > mid) return query (rson, L, R);
    	else return min (query(lson, L, mid), query(rson, mid + 1, R));
    }
    
    void update (int o, int l, int r, int L, int R, int x)
    {
    	if (l == L && r == R)
    	{
    		mini[o] += x;
    		lazy[o] += x;
    		return;
    	}
    	pushdown (o);
    	if (R <= mid) update (lson, L, R, x);
    	else if (L > mid) update (rson, L, R, x);
    	else update (lson, L, mid, x), update (rson, mid + 1, R, x);
    	mini[o] = min (mini[o << 1], mini[o << 1 | 1]);
    }
    
    void build (int o, int l, int r)
    {
    	if (r == l) 
    	{
    		mini[o] = ans[l];
    		return;
    	}
    	build (lson);
    	build (rson);
    	mini[o] = min (mini[o << 1], mini[o << 1 | 1]);
    }
    
    int main()
    {
    	scanf ("%d", &kase);
    	while (kase --)
    	{
    		dcnt = 0;
    		clr(cnt, 0), clr(ans, 0), clr(mini, 0), clr(lazy, 0), clr (id, 0), clr(last, 0);
    		scanf ("%d", &n);
    		for (int i = 1; i <= n; ++ i)
    		{
    			scanf ("%d", &xi[i]);
    			disc[++dcnt] = xi[i];
    		}
    		sort (disc + 1, disc + 1 + dcnt);
    		dcnt = unique (disc + 1, disc + 1 + dcnt) - disc - 1;
    		for (int i = n; i >= 1; -- i)
    		{
    			xi[i] = lower_bound (disc + 1, disc + 1 + dcnt, xi[i]) - disc;
    			if (id[xi[i]]) last[i] = id[xi[i]];
    			else last[i] = n + 1;
    			id[xi[i]] = i;
    		}
    			
    		int getans = 0;
    		for (int i = 1; i <= n; ++ i)
    		{
    			cnt[xi[i]]++;
    			if (cnt[xi[i]] == 1) ans[i] = ans[i - 1] + 1;
    			else if (cnt[xi[i]] == 2) ans[i] = ans[i - 1] - 1;
    			else ans[i] = ans[i-1];
    			if (ans[i] == 0) {printf ("no
    "); getans = 1; break;}
    		}
    		if (getans) continue;
    		build (1, 1, n);
    		for (int i = 2; i <= n; ++ i)
    		{
    			update (1, 1, n, i, last[i - 1] - 1, -1);
    			if (last[i - 1] <= n) update (1, 1, n, last[i - 1], last[last[i - 1]] - 1, 1);
    			if (query(1, 1, n, i, n) == 0) {printf ("no
    "); getans = 1; break;}
    		}
    		if (!getans){printf ("yes
    "); getans = 1;}
    	}
    	return 0;
    }
    

      

    这个是我一开始想的比较蠢的算法了,因为是从暴力优化来的嘛~

    方法二:

    对于一个区间[L,R]判断它合法,是有一个元素在这个区间只出现了一次,那么怎么判断一个元素在这个区间是否出现过一次呢?那么需要找到这个元素的前驱和后继的位置,判断他们是否在这个区间内,如果在那么这个元素在这个区间出现不止一次,如果这个区间合法那么就继续验证它的子区间,复杂度什么的不会证明QAQ

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 2e5 + 7;
    int id[N], L[N], R[N], disc[N], dcnt, xi[N], kase, n;
    
    void process()
    {
    	memset (id, 0, sizeof id);
    	for (int i = 1; i <= n; ++i)
    	{
    		if (id[xi[i]]) L[i] = id[xi[i]];
    		else L[i] = 0;
    		id[xi[i]] = i;
    	}
    	memset (id, 0, sizeof id);
    	for (int i = n; i >= 1; --i)
    	{
    		if (id[xi[i]]) R[i] = id[xi[i]];
    		else R[i] = n + 1;
    		id[xi[i]] = i;
    	}
    }
    
    int check (int l, int r)
    {
    	if (l >= r) return true;
    	for (int i = l, j = r; i <= j; ++ i, -- j)
    	{
    		if (L[i] < l && R[i] > r) return check (l, i - 1) && check (i + 1, r);
    		if (L[j] < l && R[j] > r) return check (l, j - 1) && check (j + 1, r);
    	}
    	return false;
    }
    
    int main ()
    {
    	scanf ("%d", &kase);
    	while (kase --)
    	{
    		dcnt = 0;
    		scanf ("%d", &n);
    		for (int i = 1; i <= n; ++ i)
    		{
    			scanf ("%d", &xi[i]);
    			disc[++dcnt] = xi[i];
    		}
    		sort (disc + 1, disc + 1 + dcnt);
    		dcnt = unique (disc + 1, disc + 1 + dcnt) - disc - 1;
    		for (int i = 1; i  <= n; ++i)
    			xi[i] = lower_bound (disc + 1, disc + 1 + dcnt, xi[i]) - disc;
    		process();
    		check (1, n) ? puts("yes") : puts("no"); 
    	}
    	return 0;
    }
    

      

    总结:

    对于这种套路感觉有点像meet in the middle 好神啊,这种做法QAQ多理解一下啦~

  • 相关阅读:
    Java项目中读取properties文件,以及六种获取路径的方法
    在eclipse中使用JUnit4,以及使用JUnit4进行单元测试的技巧
    [Evernote]印象笔记使用经验技巧
    使用Word2010发布博客文章
    Win7/8 绿色软件开机启动
    常见笔试题
    排序
    数据库知识归纳(索引)
    数据库知识归纳(事务)
    Redis
  • 原文地址:https://www.cnblogs.com/xgtao/p/6020766.html
Copyright © 2020-2023  润新知