Educational Codeforces Round 110 (Div. 2)2021.06.05

Educational Codeforces Round 110 (Rated for Div. 2)

闲话:没有闲话

A. Fair Playoff

题意

A

题解

没啥好说

code

int main()
{
    int t;cin>>t;
    while(t--){
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        int x=max(a,b),y=max(c,d);
        int xx=min(a,b),yy=min(c,d);
        if(x>yy&&y>xx)cout<<"YES\n";
        else cout<<"NO\n";
 
    }
    return 0;
}

B. Array Reodering

题意

B
给出长为n的序列,要将其重新排列,使得满足good的整数对数总数最大,某一对数a[i],a[j],满足i<j&&gcd(a[i],a[j]*2)>1则称为good。

题解

首先对偶数x而言,任意y都有gcd(x,2*y)>1,所以我们把偶数放在前面,那么它可以和后面所有的数组成good数对。对于奇数,无论怎么排都不会有影响,所以我们放在后面之后,从前往后依次判断即可。

code

int gcd(int a,int b)   //return a=gcd(a,b)
{
    ll temp;
    while(b){
        temp=b;
        b=a%b;
        a=temp;
    }
    return a;
}
const int N=2002;
int a[N];
int main()
{
    IOS
    int t;cin>>t;
    while(t--){
        int n;cin>>n;
        int yes=0;
        int ans=0;
        map<int,int>mp;
        vector<int>all;
        for(int i=0;i<n;i++){
            cin>>a[i];
            if(a[i]%2==0)yes++;
            if(a[i]%2==1){
               all.push_back(a[i]);
            }
        }
        for(int i=1;i<=yes;i++)ans+=(n-i);
        //sort(all.begin(),all.end());
        for(int i=0;i<all.size();i++){
            for(int j=i+1;j<all.size();j++){
                if(gcd(all[i],all[j])>1)ans++;
            }
        }
        cout<<ans<<'\n';
    }
    return 0;
}

C. Unstable String

重头戏来了,这是道不是很难但是做法很多很强的题,值得一看。

题意

C

一个字符串只有"0",“1”,"?"组成,’?"是可变字符,可根据需要在变化,或者说是任意字符,既是1也是0。要求输出它的beautiful的子串的总数。一个字符串称为beautiful需满足1和0交替组成。

题解

方法一

我们按区间来统计答案,这里取一段beautiful的区间[l,r],那么它的贡献就是r-l+1。所以我们可以枚举右端点,然后每次更新左端点使得[l,r]是beautiful的。接下来的问题就是如何保证该区间是beautiful的。
要判断合法可能比较难,那么我们不妨先默认合法,然后再通过判断不合法来向右移动左端点l。我们发现交替序列0101或者1010它们的奇数为和偶数位都是固定好了的。所以我们可以对区间[l,r]统计奇数位上的1,偶数位上的1,奇数位上的0,偶数位上的0。如果出现一下情况说明该区间是不合法的:

  • ji0!=0&&ou0!=0
  • ji1!=0&&ou1!=0
  • ji1!=0&&ji0!=0
  • ou1!=0&&ou0!=0

那么我们就让l++,然后根据减少的那位归属减少上述四个标记对应的值。知道以上条件均不满足。
至于问号,因为这里的四个标记是用来判断不合法的,所以?不会对不合法产生贡献,因此碰到?不需要缩小区间。

int main()
{
    int t;cin>>t;
    while(t--){
        string s;
        cin>>s;
        ll ji1=0,ji0=0,ou1=0,ou0=0;
        ll ans=0;
        ll len=s.length();
        ll l=1;//主要l和r都是1开始的
        for(ll i=1;i<=len;i++){//为了方便计算这里i是下标加一
            if(s[i-1]=='0'){
                if(i&1)ji0++;
                else ou0++;
            }
            else if(s[i-1]=='1'){
                if(i&1)ji1++;
                else ou1++;
            }
            while( (ji0&&ou0)||(ji1&&ou1)||(ji1&&ji0)||(ou1&&ou0) ){
                if(s[l-1]=='0'){
                if(l&1)ji0--;
                else ou0--;
            }
            else if(s[l-1]=='1'){
                if(l&1)ji1--;
                else ou1--;
            }
            l++;
            }
            ans+=(1ll+i-l);
        }
        cout<<ans<<'\n';
    }
    //system("pause");
    return 0;
}

方法二

另一种方法是干脆固定A=10101 B=01010,然后把串去和A和B匹配,这里借用队友画的图。把它划分成若干个连续子串。答案就是分部统计。
在这里插入图片描述
注意在计算时?有可能会重复计算
在这里插入图片描述
这是队友的代码

using namespace std;
const int N = 3e5 + 7;
const ll mod = 1e9 + 7;
char s[N]; 
int mark[N];
vector<int>v;
int main()
{
	
	int t; cin >> t;
	while (t--)
	{
		 cin >> s+1;
		int len = strlen(s+1);
		mark[0] = mark[len + 1] = -1;
		v.clear(); v.push_back(0);
		for (int i = 1; i <= len; i++)
		{
			if (s[i] != '?')
			{
				v.push_back(i);
				//存下标
				mark[i] = (s[i] - '0' + i) % 2;
				//标记这个位置对应的是A还是B
			}
		}
		v.push_back(len + 1);
		int l = 1, r;
		ll ans = 0, last = 0;
		if (v.size() == 2)ans = (len * (len + 1)) / 2;
		//如果整个串都是 ? 构成,那直接算答案
		
		else while (l < v.size() - 1)
		{
			r = l;
			while (mark[v[l]] == mark[v[r + 1]])r++;
			
			int temp = v[r + 1] - v[l - 1] - 1;
			//temp就是子序列的连续长度了
			//last是A。。。B之间那个问号序列的贡献,因为在一些过程中这个last可能会被算重复
			ans += ((temp + 1) * temp) / 2 - last;
			//减掉重复的贡献
			temp = v[r + 1] - v[r] - 1;
			//此处r+1和r对应的必定是A B或者B A,因为前面的while跑到不同处就停下来,所以相减得出问号长度
			last = (temp * (temp + 1) / 2);
			//last是A。。。B之间那个问号序列的贡献
			l = r + 1;
		}
		cout << ans << endl;
	}
}

有幸见识到更加奇妙的代码

const int N=2e5+5;
int dp[N];
int main()
{
    int t;cin>>t;
    while(t--){
        string s;
        cin>>s;
        ll ans=0,a=0,b=0,c=0;
        for(ll i=0;i<s.size();i++){
            if(s[i]=='?')a++,b++,c++;
            else if(s[i]-'0'==i%2)a++,b=0,c=0;
            else a=0,b++,c=0;
            ans+=(a+b-c);
        }
        cout<<ans<<'\n';
    }
    return 0;
}

a和b分别用来记录A和B匹配的长度,c用来记录?重复计算的部分。
个人认为这应该是看起来最舒服的版本。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值