zoukankan      html  css  js  c++  java
  • codeforces 1295 题解(完结)

    codeforces 1295 题解

    A. Display The Number

    给你可显示的总数,让你凑个最大的数,很明显,抽象的思想就是“好钢要用在刀刃上”,如果可显示的数字越大,所消耗的越小,他自然更好。

    我们观察一下各个数字的消耗情况:

    0 1 2 3 4 5 6 7 8 9
    6 2 5 5 4 5 6 3 7 6

    数字 (9) 比数字 (8,6) 要大,消耗还不比他们多,所以答案中肯定不出现 (8,6) (单调队列思想)。

    这样一看,答案中只有可能出现数字 (1,7,9) 。我们再思考一下,一个数字大,他怎么算大?每一位最大的数字大他就一定大?显然不是,判断两个数哪个大,首先要比较位数(大数思想),位数大的自然也就更大。

    我们枚举一下,如果我们的可显示总数只有 ([2,3]) 个,自然都只能凑成一位数了,但如果有 (4) 个呢?我们可以用两个 (1) 凑成两位数 (11) ,如果我们有 (6) 个就能凑成 (111),有点感觉以后我们发现好像答案中也不会出现 (9) 了,因为“两拳难敌四手”,凑成一个 (9) 的代价过高,不如用它多凑几位 (1)

    事实上,如果给你偶数个可显示总数,你就用他们都来凑 (1) 就行,最后刚刚好。如果给你奇数个,那你可以先凑一个 (7),把它放在最高位,这样也就不会有剩下的了。可以证明答案中最多出现一个 (7),因为出现两个或者以上,那不如用他们多凑几个 (1)。详细证明过程略,意思领会就好。

    时间复杂度 (O(n))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef double db;
    #define _for(i,a,b) for(int i = (a);i < b;i ++)
    #define _rep(i,a,b) for(int i = (a);i > b;i --)
    #define INF 0x3f3f3f3f3f3f3f3f
    #define ZHUO 11100000007
    #define SHENZHUO 1000000007
    #define pb push_back
    #define debug() printf("Miku Check OK!
    ")
    #define maxn 20003
    #define X first
    #define Y second
     
     
    int main()
    {
    	int t;
    	scanf("%d",&t);
    	while(t--)
    	{
    		int n;
    		scanf("%d",&n);
    		string rnt;
    		if(n&0x1)
    			n -= 3,rnt += '7';
    		while(n>0)
    			n -= 2,rnt += '1';
    		printf("%s
    ",rnt.c_str());
    	}
    	return 0;
    }
    

    B. Infinite Prefixes

    根据它给的那个 (balance) 公式,我们可以转换一下:字符串 (t) 每出现一个 (0) ,其实就是 (balance)(+1),出现一个 (1) 就是 (balance)(-1)。然后我们就能根据字符串 (t) 构造一个整数数组 (a)。我们就拿样例 (1) 说事儿:

    t 0 1 0 0 1 0
    a 1 0 1 2 1 2

    那这个数组 (a) 有啥子用?我们思考这个字符串 (t) 是怎么来的,没错,就是通过字符串 (s) 复读来的,那么我们把这个 (a) 数组往下写一写,找找规律:

    (1,0,1,2,1,2,3,2,3,4,3,4......)

    复读一遍,我们发现第 (i(i≥1)) 位这个数怎么来?我给你一个公式你看看对不对:

    (a_{k,i}=a_{1,i}+a_{1,n}×k)

    其中 (a_{i,j}) 代表第 (i) 组第 (j) 个。

    那这样我们就能在预处理出 (a) 以后快速计算出第 (?) 个数了。我们继续观察这个数组, (a) 数组里不同组但是组内相对位置相同则整体单调,所以只有末尾那个数为 (0) 的时候才有可能输出 (-1)。只有末尾那个数为 (0) 还不够,还要中间出现 (x) 这个数才输出 (-1)

    处理完特殊情况以后我们再继续思考,既然后面都是复读的,而且单调增或者减,我们的 (x) 是不变的,那么 (x) 对于组内相对位置相同时,在不同组只有可能出现 (1) 次,因此我们就看第一组然后算后面的那些个组,该位置应该是什么数,如果能等于 (x) ,那说明 (x) 可以在该相对位置出现一次,否则就不能。

    根据上面的公式随便搞一搞算一算看看能不能整除,就能判断 (x) 在该组内相对位置能否出现,能答案就 (+1) ,注意特殊考虑 (0) 的情况。

    时间复杂度 (O(n))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef double db;
    #define _for(i,a,b) for(int i = (a);i < b;i ++)
    #define _rep(i,a,b) for(int i = (a);i > b;i --)
    #define INF 0x3f3f3f3f3f3f3f3f
    #define ZHUO 11100000007
    #define SHENZHUO 1000000007
    #define pb push_back
    #define debug() printf("Miku Check OK!
    ")
    #define maxn 100003
    #define X first
    #define Y second
    
    bool judge(int a,int b)
    {
    	if(!a || (!a && !b))
    		return true;
    	if(!b)
    		return false;
    	return ((a/abs(a))*(b/abs(b)))>0 && !(a%b);
    }
    
    int T;
    int main()
    {
    	ios::sync_with_stdio(false);
    	cin >> T;
    	while(T--)
    	{
    		int n, x;string s;
    		cin >> n >> x >> s;
    		int a[maxn];
    		
    		a[0] = 0;
    		_for(i,0,n)
    			a[i+1] = a[i] + ((!(s[i]-'0')) ? 1 : -1);
    		
    		int rnt = 0;
    		if(!a[n])
    		{
    			_for(i,1,n+1)
    				if(a[i]==x)
    					rnt = -1;
    			if(rnt==-1)
    			{
    				printf("-1
    ");
    				continue;
    			}
    		}
    		
    		if(!x)
    			rnt ++;
    		_for(i,1,n+1)
    		{
    			int tmpx = x;
    			tmpx -= a[i];
    			if(judge(tmpx,a[n]))
    				rnt ++;
    		}
    		printf("%d
    ",rnt);
    	}
    	return 0;
    }
    

    C. Obtain The String

    既然是从字符串 (s) 中取子序列凑 字符串 (t) ,那就贪心的去凑就可以了:每一次从 (s) 中取出的字符串自然越长越好。

    关键是我们不能对于每个 (t) 中的字符都往后面无脑扫一遍 (s) ,那是 (O(n^2)) 的时间复杂度,那我们就想点好方法快点扫不就完事儿了吗!怎么快呢,我们预处理 (dp[i][j]) 表示在字符串 (s) 下标为 (i) 的地方,后面最靠近该位置的字母 (j+'a') 的下标是多少 ,这样到时候我们就能跳着扫 (s) ,这样就快的多了。

    输出 (-1) 的情况既很好判断也很好理解:字符串 (t) 中出现了 字符串 (s) 中没有的字母自然就凑不出来咯!

    时间复杂度 (O(max(|s|,|t|)))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef double db;
    #define _for(i,a,b) for(int i = (a);i < b;i ++)
    #define _rep(i,a,b) for(int i = (a);i > b;i --)
    #define INF 0x3f3f3f3f3f3f3f3f
    #define ZHUO 11100000007
    #define SHENZHUO 1000000007
    #define MIKUNUM 39
    #define pb push_back
    #define debug() printf("Miku Check OK!
    ")
    #define maxn 100003
    #define X first
    #define Y second
    
    //MIKUNUM是我喜欢的数字,没啥意义,你也可以改成,40,50,或者30,都行
    int T;
    string s, t;
    int latest[MIKUNUM];
    int dp[maxn][MIKUNUM];
    
    bool judge()
    {
    	_for(i,0,t.size())
    		if(latest[t[i]-'a']==-1)
    			return false;
    	return true;
    }
    int main()
    {
    	ios::sync_with_stdio(false);
    	cin >> T;
    	while(T--)
    	{
    		cin >> s >> t;
            //这里初始化的可能有点多了,放在程序最后然后根据当前输入量初始化能省点时间
    		memset(latest,-1,sizeof(latest));
    		memset(dp,-1,sizeof(dp));
    		
    		_rep(i,s.size()-1,-1)
    		{
    			_for(j,0,26)
    				dp[i][j] = latest[j];
    			latest[s[i]-'a'] = i;
    		}
    		
    		if(!judge())
    		{
    			printf("-1
    ");
    			continue;
    		}
    		
    		int rnt = 0;
    		int s_in = -1;
    		_for(i,0,t.size())
    		{
    			if(s_in==-1)
    			{
    				rnt ++;
    				s_in = latest[t[i]-'a'];
    				continue;
    			}
    			s_in = dp[s_in][t[i]-'a'];
    			(s_in == -1) ? i-- : MIKUNUM;
    		}
    		printf("%d
    ",rnt);
    	}
    	return 0;
    }
    

    D. Same GCDs

    数论题,卓爷的锅,我就不背了,(\%ZHUO)

    E. Permutation Separation

    我们看到这题,自然而然应该就会想到两个问题:

    • 划分的依据是什么(划分完左右各有哪些元素)
    • 如何移动是最优的

    如果能把这两个子问题搞定,那答案也就不难出来了。我们再读题,他给了个排列,也就是数组 (p) 里的元素是从 (1)(n) 的,那我们现在看第一个子问题,全都移动好以后左面无非就是 (1)(1,2)(1,2,3)(1,2,3,4) ... ...这些个情况。

    我们再考虑,现在随便选一个位置,那么左边要移动什么到右边,右边要移动什么到左边就确定了。比如你说左边有 (3) 个数,那无非左边就是 (1,2,3) ,所以如果 (1,2,3) 有在你分的地方的右边那么就要要移动到左边,左边除了这三个元素都要移动到右边。敲定一开始的划分位置和最终左边数组的大小,暴力 (O(n^2)) 算法成了,但数据范围肯定 (T) 飞。

    考虑一下怎么优化,我们搞个代价数组 (a) 的前缀和数组出来,然后求下标为 ([1,n-1]) 的最小值即可。这是什么?这就是你假设全移动完以后左边是空着的,所有数都在右边的答案,取最小值其实就是枚举一开始在哪儿划分的过程。那我们只要枚举左边数组的大小,然后答案取各个不同数组大小情况下的最小值不就行了吗?(之所以是 ([1,n-1]) ,是因为我们假设划分是在该下标的数的右边,如果取 (n),说明一开始就划分出一个 “一边空”,那自然不符合题意)。

    枚举左边数组大小那必定对时间复杂度有个 (n) 的贡献没跑了,不过我们区间查最小值可以优化到 (logn) 。对于一个在 数组(p) 中值为 (i(1≤i≤n)) 的数,我们可以让前缀和数组右边都减去 (a[pos[i]])(pos[i])(i) 在原数组中的位置),左边都加上 (a[pos[i]]) ,也就是我们钦定他最终一定在左边数组,即如果一开始划分在该数左面,这数肯定要移动到左边数组,就要花费他的代价,同理如果一开始划分在该数右面,这数也不用移动了,但是本来我们是算了前缀和的,是考虑到他要移动,因此要把这个代价减去。这两步做完以后直接查最小值最后统筹一下答案就行了。

    这个什么区间加减·区间查询 用线段树维护一下就行了,注意下标和细节边界条件!

    时间复杂度 (O(nlogn))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef double db;
    #define _for(i,a,b) for(int i = (a);i < b;i ++)
    #define _rep(i,a,b) for(int i = (a);i > b;i --)
    #define INF 0x3f3f3f3f3f3f3f3f
    #define ZHUO 11100000007
    #define SHENZHUO 1000000007
    #define MIKUNUM 39
    #define pb push_back
    #define debug() printf("Miku Check OK!
    ")
    #define maxn 200003
    #define X first
    #define Y second
     
    int n;
    ll p[maxn];
    ll a[maxn];
    ll pos[maxn];
    struct SegTree
    {
    	struct SegNode
    	{
    		int l,r;
    		ll add;
    		ll min;
    		#define l(x) tree[x].l
    		#define r(x) tree[x].r
    		#define add(x) tree[x].add
    		#define MIN(x) tree[x].min
    	}tree[maxn<<2];
    	void build(int p,int l,int r)
    	{
    		l(p) = l,r(p) = r;
    		if(l==r)
    		{
    			MIN(p) = a[l];
    			return ;
    		}
    		int mid = (l+r)/2;
    		build(p*2, l, mid);
    		build(p*2+1, mid+1, r);
    		MIN(p) = min(MIN(p*2),MIN(p*2+1));
    	}
    	void spread(int p)
    	{
    		if(add(p))
    		{
    			MIN(p*2) += add(p);
    			MIN(p*2+1) += add(p); 
    			add(p*2) += add(p);
    			add(p*2+1) += add(p);
    			add(p) = 0;
    		} 
    	}
    	void change(int p,int l,int r,ll d)
    	{
    		if(l <= l(p) && r >= r(p))
    		{
    			MIN(p) += d;
    			add(p) += d;
    			return ;
    		} 
    		spread(p);
    		int mid = (l(p)+r(p))/2; 
    		if(l <= mid)
    			change(p*2, l, r, d);
    		if(r > mid)
    			change(p*2+1, l, r, d);
    		MIN(p) = min(MIN(p*2),MIN(p*2+1));
    	} 
    	ll ask4min(int p,int l,int r)
    	{
    		if(l <= l(p) && r >= r(p))
    			return MIN(p);
    		spread(p);
    		int mid = (l(p)+r(p))/2;
    		ll val = INF;
    		if(l <= mid)
    			val = min(val,ask4min(p*2, l, r));
    		if(r > mid)
    			val = min(val,ask4min(p*2+1, l, r));
    		return val;
    	}
    }T;
    int main()
    {
    	scanf("%d",&n);
    	_for(i,1,n+1)
    	{
    		scanf("%lld",&p[i]);
    		pos[p[i]] = i;
    	}
    	_for(i,1,n+1)
    		scanf("%lld",&a[i]);
    	
    	T.build(1,1,n);
    	_for(i,1,n)
    		T.change(1, i+1, n, a[i]);
    		
    	ll rnt = T.ask4min(1, 1, n-1);
    	_for(i,1,n+1)
    	{
    		if(pos[i]>1)
    			T.change(1, 1, pos[i]-1, a[pos[i]]);
    		T.change(1, pos[i], n, -a[pos[i]]);
    		rnt = min(rnt,T.ask4min(1, 1, n-1));
    	}
    	printf("%lld
    ",rnt);
    	return 0;
    }
    

    F. Good Contest

    数论概率论,还是卓爷的锅,卓爷 (tql)

  • 相关阅读:
    check_mysql.sh
    shell 数组长度
    Shell脚本中计算字符串长度的5种方法
    非缓冲文件编程(实时操作)
    ferror,clearerr和EOF含义
    密码库生成
    筛选出多个数据并判断
    扫描有分隔符的数据
    unicode文件处理(如果是ANSI编码就不需要了)
    ferror,perror,cleaner
  • 原文地址:https://www.cnblogs.com/Asurudo/p/12242236.html
Copyright © 2011-2022 走看看