Educational Codeforces Round 110 (Rated for Div. 2)
闲话:没有闲话
A. Fair Playoff
题意
题解
没啥好说
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
题意
给出长为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
重头戏来了,这是道不是很难但是做法很多很强的题,值得一看。
题意
一个字符串只有"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用来记录?重复计算的部分。
个人认为这应该是看起来最舒服的版本。