Always keep |

园龄:粉丝:关注:

牛客周赛 Round 72

怎么全是01串

A

枚举除了末尾的字符,判断下一个是否与它不同,不同则对答案的贡献++

B

找一个连续子串是好串,如果我们找到长度为len的子串,那么从中任意截取一段均为好串

长度为len的子串 1个
长度为len-1的子串 2个
.....
长度为2的子串 len-1个

用等差数列公式
一个长度为len的好串子串的个数为i=1len1=len(len1)2

C

构造一个解的情况还是很容易的,但是一些情况需要特判

以下情况都不能构成

  • k==0 此时有0有1则不可能构造
  • a==bk>min(a,b)21 此时的a个0和b个1,形成的01串最多有min(a,b)*2-1对不相邻的01,因为,长度为a+b的串最多有a+b-1对01,而此时a==b
  • a!=bk>min(a,b)2 假设此时b>a ,那么可以拿a个0和b个1构成上述的a*2-1对字符串,而此时1有多余的,还可以加入到字串末尾多形成一对01

可以构造的情况就简单了
先生成足够01,然后将多余的0,1放在两边

#include<bits/stdc++.h>
using namespace std;
int t;
int a,b,k;

int main(){
    cin>>t;
    while(t--){
        cin>>a>>b>>k;
        if(k==0){
            if(a && b){
                cout<<-1<<endl;
            }
            else {
                while(a){--a;cout<<"0";}
                while(b){--b,cout<<"1";}
                cout<<endl;
            }
        }
        else {
                if(a==b && k>min(a,b)*2-1) cout<<-1<<endl;
                else if(a!=b && k>min(a,b)*2){
                    cout<<"-1"<<endl;
                }
                else {
                    if(k&1){
                        int mid=k+1;
                        for(int i=1;i<=a-mid/2;++i) cout<<0;
                        for(int i=1;i<=mid/2;++i) cout<<"01";
                        for(int i=1;i<=b-mid/2;++i) cout<<1;
                    }
                    else {
                        int mid=k;
                        if(a>=b){
                            for(int i=1;i<=a-mid/2-1;++i) cout<<0;
                            for(int i=1;i<=mid/2;++i) cout<<"01";//只有k-1对01
                            for(int i=1;i<=b-mid/2;++i) cout<<1;
                            cout<<0;  //补上一个
                        }
                        else{
                            cout<<1;//前面补上
                            for(int i=1;i<=a-mid/2;++i) cout<<0;
                            for(int i=1;i<=mid/2;++i) cout<<"01";//只有k-1对
                            for(int i=1;i<=b-mid/2-1;++i) cout<<1;

                        }
                    }cout<<endl;
                }
                
        }
        
    }return 0;
}

D

一眼DP,但是状态转移时顺序错误了
应该从后往前转移

由题可知,只能转移最近相同字符和不同字符,

如果按照顺序来转移,举例00110,转移到第二个1时,最近的0是第一个1前一个,但因为0只能转移到最近的1,所以不能从这个0转移
这里的错误在于,忽略了01之间存在其他的有效转移,导致转移出错

如果按照逆序来转移,同样的例子,转移第一个1前面的0时,只能第一个1转移,末尾的0转移,这样逆转过来的顺序便不会出错
再深究一点,因为每一个位置,只有唯一的向后转移的顺序,所以反过来找时唯一的

#include<bits/stdc++.h>

using namespace std;

int n;
const int maxn=1e5+10;
int x,y;
int l1=-1,l0=-1;
string s;
long long  f[maxn];
int main(){
    cin>>n>>x>>y;
    cin>>s;
    for(int i=0;i<=n;++i) f[i]=1e15+10;
    f[n-1]=0;
    if(s[n-1]=='0') l0=0;
    else l1=0;
    for(int i=n-1;i>=0;--i){
        if(s[i]=='0'){
            if(l0!=-1)f[i]=min(f[i],f[l0]+x);
            if(l1!=-1)f[i]=min(f[i],f[l1]+y) ;   
            l0=i;
        }
        else{
            if(l0!=-1)f[i]=min(f[i],f[l0]+y);
            if(l1!=-1)f[i]=min(f[i],f[l1]+x);    
            l1=i;
        }
     
    }cout<<f[0]<<endl;
    return 0;
}

E

首先,不要误解题目,01串代表的正整数,不是二进制数
根据同余的性质,同乘性和同加性,一个数从高位乘到低位,余数不变

定义
𝑑𝑝ij表示前i个位置,取模k的值为j的方案总数
转移方程

            if(s[i+1]=='0')//是0,乘10,取余
                dp[i+1][j*10%13]+=dp[i][j];
            else if(s[i+1]=='1')//是1,乘10+1,取余
                dp[i+1][(j*10+1)%13]+=dp[i][j];
            else {//是?两种情况都考虑
                dp[i+1][(j*10+1)%13]+=dp[i][j];
                dp[i+1][j*10%13]+=dp[i][j];
            }

有人可能有疑问,只是有?的地方才需要状态转移把,如果?前面都是01,那么构成的整数同余的方案数不会变啊,只需要用最后两个状态转移啊
但是如果一旦经过了?,那么上面的转移方程就都需要有用了

#include<bits/stdc++.h>

using namespace std;
int n;
#define ll long long
const int maxn=2e5+10,mod=1e9+7;
string s;
ll dp[maxn][16];
int main(){
    cin>>s;
    dp[0][0]=1;//初始化
    n=s.length();s=" "+s;
    
    for(int i=0;i<n;++i)
        for(int j=0;j<=12;++j){
            if(s[i+1]=='0')
                dp[i+1][j*10%13]+=dp[i][j];
            else if(s[i+1]=='1')
                dp[i+1][(j*10+1)%13]+=dp[i][j];
            else {
                dp[i+1][(j*10+1)%13]+=dp[i][j];
                dp[i+1][j*10%13]+=dp[i][j];
            }
            dp[i+1][j*10%13]%=mod;
            dp[i+1][(j*10+1)%13]%=mod;
        }
    cout<<dp[n][0];//是13的倍数,取余后为0
}

F

建议看这个题解

我来讲讲补完这个题的收获吧

  1. 线段树离散化

将查询区间端点排序,重新构成小区间,将每个小区间作为叶节点,这样查询的时候一定能查到由小区间构成的区间
建树时的区间应该为端点个数来建立

  1. 明确父节点和子节点的关系

只有将父节点,子节点的关系明确,才能进行push_down,push_up,以及设置懒标记的操作

  1. 确定节点的信息

节点的信息对push_down,push_up,lazy等相当重要,而且往往节点信息是我们所求的答案

  1. push_down

每次的push_down要清除,当前节点的懒标记,并让子节点继承,而且子节点的信息会根据懒标记更新,而且如果子节点也有懒标记,那么就要思考怎么处理父节点懒标记和子节点懒标记的继承问题

#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
using namespace std;
const int maxn=3e5+10;
int t;vector<int>v;

int m,n,q;
int getidx(int x){
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
struct qy{
	int op,l,r;
}a[maxn];
struct node{
	int l,r;
	int ranl,ranr;
	int num0,num1;
	int laze_tag;
	int lazy_1; 
}tr[maxn<<2];

void cal(int p,int lt,int l1){
	int len=tr[p].ranr-tr[p].ranl+1;
	//先1后翻,全部变零 
	if(lt && l1 && lt>l1){
		tr[p].num0=len/2;
		tr[p].num1=(len+1)/2; 
	}
	//翻后 1操作 
	else if(l1){
		//此时全为1 
		tr[p].num0=(len+1)/2; 
		tr[p].num1=len/2;
	}
	//全部翻转,变好串次数交换 
	else if(lt){
		swap(tr[p].num0,tr[p].num1);
	} 
}
void push_down(int p){
	if(tr[p].lazy_1) tr[ls].lazy_1=tr[rs].lazy_1=tr[p].lazy_1;
	//down下去取反操作 
	if(tr[p].laze_tag){//根据父节点,确定子节点,懒标记 
		if(tr[ls].lazy_1<tr[ls].laze_tag)
			tr[ls].laze_tag=0;
		else tr[ls].laze_tag=tr[p].laze_tag;
		
		if(tr[rs].lazy_1<tr[rs].laze_tag)
			tr[rs].laze_tag=0;
		else tr[rs].laze_tag=tr[p].laze_tag;
	} 
    //更改num0,num1
	cal(ls,tr[p].laze_tag,tr[p].lazy_1);
	cal(rs,tr[p].laze_tag,tr[p].lazy_1);
	tr[p].laze_tag=tr[p].lazy_1=0; 
}
void push_up(int p){
	tr[p].ranl=tr[ls].ranl;
	tr[p].ranr=tr[rs].ranr;
	
	tr[p].num0=tr[ls].num0;
	int lenl=tr[ls].ranr-tr[ls].ranl+1;
	////左边区间长度是奇数且0开头,那么末尾是0,
	//就可以和右边的1开头区间合并 
	if(lenl & 1) tr[p].num0+=tr[rs].num1; 
	//偶区间,结尾是1,右边0开头的区间合并 
	else tr[p].num0+=tr[rs].num0;
	
	//与上面同理 
	tr[p].num1=tr[ls].num1;
	if(lenl & 1)	tr[p].num1+=tr[rs].num0;
	else tr[p].num1+=tr[rs].num1;
	
}

void build(int p,int l,int r){
	tr[p].l=l;tr[p].r=r;
	if(l==r){
		
		//区间离散化 
		tr[p].ranl=v[l-1];
		tr[p].ranr=v[l]-1;
		int len=tr[p].ranr-tr[p].ranl+1;
		//一开始全为0 
		tr[p].num0=len/2;//修改区间成 以0开头的好串的最小修改次数 
		tr[p].num1=(len+1)/2;//修改区间成 以1开头的好串的最小修改次数 
		return ; 
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(p);
}
void update(int p,int nl,int nr,int op,int num){
	int l=tr[p].l,r=tr[p].r;
	if(nl<=l && r<=nr){
		if(op==1){
			tr[p].lazy_1=num;//记录最近的1操作是第几次 
			cal(p,0,num); 
		}
		if(op==2){
			if(tr[p].lazy_1<tr[p].laze_tag) tr[p].laze_tag=0;
			else tr[p].laze_tag=num;
			cal(p,num,0);
		}
		return;
	} 
	push_down(p);
	int mid=(l+r)>>1;
	if(nl<=mid) update(ls,nl,nr,op,num);
	if(nr>mid) update(rs,nl,nr,op,num);
	push_up(p);
}
node query(int p,int nl,int nr){
	int l=tr[p].l,r=tr[p].r;
	if(nl<=l && r<=nr) return tr[p];
	push_down(p);
	int mid=(l+r)>>1;
	node ans;
	ans.num0=-1;
	if (nl <= mid) {
        node tmp=query(ls, nl, nr);
        ans=tmp;
    }
	if(nr>mid) {
		node tep=query(rs,nl,nr);
		if(ans.num0==-1) ans=tep;
		else {
			int len1=ans.ranr-ans.ranl+1;
			if(len1 & 1) ans.num0+=tep.num1;
			else ans.num0+=tep.num0;
			if(len1 & 1) ans.num1+=tep.num0;
			else ans.num1+=tep.num1;
			ans.ranr=tep.ranr;
		} 
	}return ans;
}

int main(){
	cin.tie(0);cout.tie(0);
	ios::sync_with_stdio(0);
	cin>>m>>q;
	
	for(int i=1;i<=q;++i){
		cin>>a[i].op>>a[i].l>>a[i].r; 
		v.push_back(a[i].l);
		v.push_back(a[i].r+1);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	int n=v.size()-1;
	build(1,1,n);
	for(int i=1;i<=q;++i){
		int op=a[i].op;
		int l=getidx(a[i].l),r=getidx(a[i].r+1)-1;
		if(op==1) update(1,l,r,1,i);
		else if(op==2) update(1,l,r,2,i);
		else{
			node ans=query(1,l,r);
			cout<<min(ans.num0,ans.num1)<<endl;
		}
	}
	return 0;
} 

本文作者:归游

本文链接:https://www.cnblogs.com/guiyou/p/18620857

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   归游  阅读(4)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起