NC19469 01串

Posted 空白菌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NC19469 01串相关的知识,希望对你有一定的参考价值。

题目链接

题目

题目描述

I used to believe
We were burning on the edge of something beautiful
Something beautiful
Selling a dream
Smoke and mirrors keep us waiting on a miracle
On a miracle
Say go through the darkest of days
Heaven\'s a heartbreak away
Never let you go
Never let me down
OH it\'s been a hell of a ride
Driving the edge of a knife
Never let you go
Never let me down
Bieber拥有一个长度为n的01 串,他每次会选出这个串的一个子串作为曲谱唱歌,考虑该子串从左往右读所组成的二进制数P。 Bieber每一秒歌唱可以让P增加或减少 2 的 k次方(k由Bieber选定),但必须保证任意时刻其P大于等于0。
Bieber 是一位追求效率的人 每次Bieber都想知道他歌唱的最少时间将这个数P变成0。
Bieber 正和 一位DJ合作,他随时可能修改串上的一个字符。

输入描述

第一行一个数n
第二行一个长度为n的字符串s
第三行一个数 t 表示 询问 + 修改总次数
以下 t 行, 每行格式如下
第一个数 1 <= type <= 2 表示 类型
Type = 1 表示是一次 询问 接下来两个数 l , r 表示询问的区间。
否则 表示一次修改 接下来两个数x,y 表示把 s[x] 改为y.
n <= 3e5, t <= 3e5

输出描述

对于每个询问输出一个数表示最少次数。

示例1

输入

4
1101
1
1 1 4

输出

3

题解

知识点:动态dp,区间dp,线段树。

先考虑不带修改操作只有询问,那就是一道简单的区间dp题,复杂度是 \\(O(n^3) \\sim O(1)\\) 。当然如果只询问 \\([1,n]\\) ,那就是线性dp了,复杂度 \\(O(n)\\)

\\(f_l,r,i,j\\) 代表考虑区间 \\([l,r]\\) ,左端点状态为 \\(i(0/1)\\) (是否向高位进位),右端点状态为 \\(j(0/1)\\) (是否从低位进位)时的操作次数最小值。

显然,两个区间合并成一个区间时,分割点位置是无后效性的。因为根据现有的状态,一个子区间状态的答案以及方案和包含它的区间的状态没有任何关系,可以推得无论分割点在哪,答案是固定的。

因此,状态转移方程为:

\\[\\beginaligned f_l,r,i,j = \\min(f_l,mid,i,0 + f_mid+1,r,0,j,f_l,mid,i,1 + f_mid+1,r,1,j) \\endaligned \\]

其中 \\(mid\\)\\([l,r]\\) 任选一点即可。

但是现在要求能够修改,那就必须使用线段树维护区间的dp信息了,也就是动态dp,每个线段树区间 \\([l,r]\\) 维护一个矩阵 \\(f[0/1][0/1]\\) 即可。维护信息的过程是十分显然的,因为此区间dp和分割点无关,就直接按照线段树的修改合并查询就做完了。

另外,区间信息的单位元值不好找,可以直接合并时特判,或者合并前排除无效区间,都可以的。

时间复杂度 \\(O((n+m)\\log n)\\)

空间复杂度 \\(O(n)\\)

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

struct T 
    array<array<int, 2>, 2> f =  (int)1e9,(int)1e9,(int)1e9,(int)1e9 ;
    friend T operator+(const T &a, const T &b) 
        // 这里用特殊值判断无效区间
        // 当然,也可以在递归的时候直接避免掉无效区间,而不是在合并时才判断
        if (a.f[0][0] == 1e9) return b;
        if (b.f[0][0] == 1e9) return a;
        auto x = T();
        //* 广义矩阵乘法(乘改加,加改取最大值)
        for (auto i :  0,1 )
            for (auto j :  0,1 )
                for (auto k :  0,1 )
                    x.f[i][j] = min(x.f[i][j], a.f[i][k] + b.f[k][j]);
        return x;
    
;

struct F 
    bool upd;
    T operator()(const T &x) 
        return
            upd,1,
            1,!upd
        ;
    
;

template<class T, class F>
class SegmentTree 
    int n;
    vector<T> node;

    void update(int rt, int l, int r, int x, F f) 
        if (r < x || x < l) return;
        if (l == r) return node[rt] = f(node[rt]), void();
        int mid = l + r >> 1;
        update(rt << 1, l, mid, x, f);
        update(rt << 1 | 1, mid + 1, r, x, f);
        node[rt] = node[rt << 1] + node[rt << 1 | 1];
    

    T query(int rt, int l, int r, int x, int y) 
        if (r < x || y < l) return T();
        if (x <= l && r <= y) return node[rt];
        int mid = l + r >> 1;
        return query(rt << 1, l, mid, x, y) + query(rt << 1 | 1, mid + 1, r, x, y);
    

public:
    SegmentTree(int _n = 0)  init(_n); 
    SegmentTree(const vector<T> &src)  init(src); 

    void init(int _n) 
        n = _n;
        node.assign(n << 2, T());
    
    void init(const vector<T> &src) 
        assert(src.size() >= 2);
        init(src.size() - 1);
        function<void(int, int, int)> build = [&](int rt, int l, int r) 
            if (l == r) return node[rt] = src[l], void();
            int mid = l + r >> 1;
            build(rt << 1, l, mid);
            build(rt << 1 | 1, mid + 1, r);
            node[rt] = node[rt << 1] + node[rt << 1 | 1];
        ;
        build(1, 1, n);
    

    void update(int x, F f)  update(1, 1, n, x, f); 

    T query(int x, int y)  return query(1, 1, n, x, y); 
;

int main() 
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<T> a(n + 1);
    for (int i = 1;i <= n;i++) 
        char x;
        cin >> x;
        a[i] = 
            x == \'1\',1,
            1,x != \'1\'
        ;
    
    SegmentTree<T, F> sgt(a);
    int m;
    cin >> m;
    while (m--) 
        int op;
        cin >> op;
        if (op == 1) 
            int l, r;
            cin >> l >> r;
            cout << sgt.query(l, r).f[0][0] << \'\\n\';
        
        else 
            int x;
            bool val;
            cin >> x >> val;
            sgt.update(x,  val );
        
    
    return 0;

NC16644

NC16644

题目描述

在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d-h”或“4-8”的子串,我们就把它当作一种简写,输出时,用连续递增的字母或数字串替代其中的减号,即,将上面两个子串分别输出为“defgh”和“45678”。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
(1)遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号“-”,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。
(2)参数p1:展开方式。p1=1时,对于字母子串,填充小写字母;p1=2时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3时,不论是字母子串还是数字子串,都用与要填充的字母个数相同的星号“*”来填充。
(3)参数p2:填充字符的重复个数。p2=k表示同一个字符要连续填充k个。例如,当p2=3时,子串“d-h”应扩展为“deeefffgggh”。减号两侧的字符不变。
(4)参数p3:是否改为逆序:p3=1表示维持原有顺序,p3=2表示采用逆序输出,注意这时仍然不包括减号两端的字符。例如当p1=1、p2=2、p3=2时,子串“d-h”应扩展为“dggffeeh”。
(5)如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:“d-e”应输出为“de”,“3-4”应输出为“34”。如果减号右边的字符按照ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:“d-d”应输出为“d-d”,“3-1”应输出为“3-1”。

解析

这是一道纯模拟题,绕来绕去的。主要是判断什么时候输出-,什么时候输出字母或数字。
对于当前字符,分以下几种情况:
1.不是’-‘时
2.是’-’,但是是最后一位
3.是’-’,但是上一位也是’-’
4.是’-’,但是下一位也是’-’
5.下一位是可以展开的字符

1-4都是原样输出,遇到5时需要进行判断

#include<bits/stdc++.h>
using namespace std;

int p1,p2,p3;

void printstr(char l,char r)
	//l不小于r,或者两端字符类型不同
	if(l>=r||(isdigit(l)&&isalpha(r))||(isalpha(l)&&isdigit(r))) 
		//原样输出 
		printf("-",l,r);
	
	//若两者只差1
	else if(l+1==r)
		//省略- 
		return ;
	 
	//否则就展开
	else 
		//如果是字母
		if(isalpha(l))
			if(p1==1)
				l=tolower(l);
				r=tolower(r);
			
			else if(p1==2)
				l=toupper(l);
				r=toupper(r);
			
		
		//判断正序倒序 
		if(p3==1) 
			//正序
			for(char i =l+1;i<r;i++)
				//输出p2个
				for(int j=0;j<p2;j++)
					//判断p1
					printf("%c",(p1==3)?'*':i); 
				 
			 
		
		else if(p3==2)
			for(char i=r-1;i>l;i--)
				for(int j=0;j<p2;j++)
					printf("%c",(p1==3)?'*':i);
				
			
		
	


int main()
	//输入参数 
	scanf("%d%d%d",&p1,&p2,&p3);
	//读入字符串
	string str;
	cin>>str;
	
	//特判开头的-
	int start;
	char l;
	for(int i=0;i<str.size();i++)
		if(str[i]=='-')
			//如果开头是-,就输出 
			printf("-");
		
		else
			//否则就下移,找到开头为字符的地方 
			start=i;
			break;
		
	 
	//对于剩余的字符
	for(int i=start;i<str.size();i++)
		//如果不是-
		if(str[i]!='-')
			printf("%c",str[i]);
		 
		//是-,但是是最后一位,或者上一位或下一位也是- 
		else if((i+1==str.size())||(i+1!=str.size()&&str[i+1]=='-')||(l=='-'))
			printf("-");
		
		//否则就展开
		else
			printstr(l,str[i+1]);
		 
		//记录上一位字符 
		l=str[i];
	 
	return 0;

以上是关于NC19469 01串的主要内容,如果未能解决你的问题,请参考以下文章

NC21874 好串

牛客题霸 NC28 最小覆盖子串

NC21874 好串

NC16644

NC16644

NC13230合并回文子串