目录

Codeforces 1323 div2 题解 (ABCD)

比赛链接 (opens new window)

日常记录自己的菜鸡行径。

虽然我打的是 div1,但是我深刻认识到我水平其实只有 div2 的(确信),不然我也不会狂掉 101 分。

# A. Even Subset Sum Problem

签到题,略。

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
const int N=1000010;
vector<int> a[2];
signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0);
    int t=read();
    while(t--){
        a[0].clear();
        a[1].clear();
        int n=read();
        repeat(i,1,n+1){
            int x=read()%2;
            a[x].push_back(i);
        }
        if(!a[0].empty())cout<<1<<endl<<a[0][0]<<endl;
        else if(a[1].size()>1)cout<<2<<endl<<a[1][0]<<' '<<a[1][1]<<endl;
        else cout<<-1<<endl;
    }
    return 0;
}

# B. Count Subrectangles

这题居然在 B 题位置

首先要统计一下序列 ab 的所有连续 1 区间长度,记 A_ia 序列第 i 个连续 1 的区间长度,B_i 类似。

考虑 k 的一个约数 d,那么 d×k/d 矩形的个数就是大于 0(A_i-d+1) 之和乘以大于 0(B_i-k/d+1) 之和,记这个数为 cnt(d)

最终答案就是所有的 cnt(d) 加起来,即 ∑_{d|k}cnt(d)

加速的话可以将 A_iB_i 排个序后求前缀和,用上 lower_bound

总时间复杂度大概 O(√(nm)log(nm))

更:根本不用前缀和,我发现去掉前缀和复杂度没什么变化,所以只要 lower_bound 就行了。

再更:连 lower_bound 都不用也过了,虽然我有点没看懂

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
const int N=40010;
vector<int> divisor;
int a[N],b[N],a_sum[N],b_sum[N]; //这个a,b实际上是题解中的A,B
void get_divisor(int n){ //求出所有约数,这是我研究出来(大雾)的数论分块求法
    for(int i=1;i<n;i=n/(n/(i+1)))
    if(n%i==0)
        divisor.push_back(i);
    divisor.push_back(n);
}
int input(int a[],int a_sum[],int n){ //读入、排序、计算前缀和
    int len=0,cnt=0;
    a[cnt++]=0;
    repeat(i,0,n){
        if(read())len++;
        else if(cnt>0)a[cnt++]=len,len=0;
    }
    if(len>0)a[cnt++]=len;
    sort(a,a+cnt);
    repeat(i,1,cnt)a_sum[i]=a_sum[i-1]+a[i];
    return cnt;
}
ll count(int a[],int a_sum[],int n,int d){ //计算所有大于0的(ai-d+1)之和
    int p=lower_bound(a,a+n,d)-a;
    ll ans=a_sum[n-1]-a_sum[p-1]-(n-p)*(d-1);
    return ans;
}
signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0);
    int n=read(),m=read();
    ll k=read(); get_divisor(k);
    n=input(a,a_sum,n); m=input(b,b_sum,m);
    ll ans=0;
    for(auto d:divisor){
        ans+=count(a,a_sum,n,d)*count(b,b_sum,m,k/d);
    }
    cout<<ans<<endl;
    return 0;
}

# C. Unusual Competitions

div1 签到题。

括号序列的一种常见处理方式是把 '(' 看作 1,把 ')' 看作 -1,然后求前缀和。

这时候前缀和小于 0 的区间(再加上区间之后的那个位置)就是需要重排的区间,把区间长度 +1 计入答案中。

比如样例中的 ))((())( 有:

) ) ( ( ( ) ) (
括号对应的值 -1 -1 1 1 1 -1 -1 1
前缀和 -1 -2 -1 0 1 0 -1 0
是否要重排 × ×

(如果前缀和最后一个位置不是 0 直接输出 -1 就行了。)

#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
const int N=1000010;
string s;
int H[N],*h=H+1,n,ans=0;
signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0);
    cin>>n>>s;
    repeat(i,0,n){
        h[i]=h[i-1]+(s[i]=='(')*2-1;
    }
    if(h[n-1]!=0)cout<<-1<<endl,exit(0);
    repeat(i,0,n)ans+=(h[i]<0)+(h[i]<0 && h[i-1]>=0);
    cout<<ans<<endl;
    return 0;
}

# D. Present

div1 的第二题,我成功被难住。

更:只能说这种题目做太少了,题解思路很牛逼,菜是原罪

我们分析答案的每一位,如果答案的第 i 位是 1,这意味着有奇数个 (j,k) 满足 a_j+a_k 的第 i 位是 1

首先我们要砍掉高于 i 位的信息(这对答案不产生影响),即让 b[j]=a[j]%(1<<(i+1))(好处是这些数字两两之和被限制得很小),于是第 i 位是 1b_j+b_k 的值只能在区间 [2^i,2^{i+1}-1]∪[2^{i+1}+2^i,2^{i+2}-1] 中。

这时如果固定 j 的值,b_k 的值的区间就是 [2^i-b_j,2^{i+1}-1-b_j]∪[2^{i+1}+2^i-b_j,2^{i+2}-1-b_j],因此能在排序后用 lower_bound 快速计算满足 b_j+b_k 的第 i 位是 1k 的个数。

复杂度是 O(n log n log C)

题解里留了个问题:Can you do it in O(n log C)?

不会,告辞!(有思路的可以告诉我 orz)

说明一下其他题解里的 [...]∪[...,2^{i+2}-2][...]∪[...,2^{i+2}-1] 是没区别的,因为最大只能是 2^{i+2}-2

//代码大部分复制于%%%lzj
#include <bits/stdc++.h>
using namespace std;
#define repeat(i,a,b) for(int i=(a),_=(b);i<_;i++)
#define repeat_back(i,a,b) for(int i=(b)-1,_=(a);i>=_;i--)
typedef long long ll; ll read(){ll x; if(scanf("%lld",&x)==-1)exit(0); return x;}
const int N=400010;
int a[N],b[N];
signed main(){
    int n=read();
    repeat(i,0,n)a[i]=read();
    int ans=0;
    repeat(i,0,26){ //答案一定小于1<<26
        int mod=1<<(i+1);
        repeat(j,0,n)b[j]=a[j]%mod;
        int s=0;
        sort(b,b+n); //排序方便二分查找
        repeat(j,0,n){
            int l,r;
            l=lower_bound(b,b+n,(1<<i)-b[j])-b;
            r=lower_bound(b,b+n,(1<<(i+1))-b[j])-b-1;
            s+=(r-l+1); //统计2^i到2^(i+1)-1区间内的解
            l=lower_bound(b,b+n,(1<<i)+(1<<(i+1))-b[j])-b;
            r=lower_bound(b,b+n,(1<<(i+2))-b[j])-b-1;
            s+=(r-l+1); //统计2^(i+1)+2^i到2^(i+2)-1区间内的解
            if((b[j]+b[j])&(1<<i))s--; //自己和自己相加的不算
        }
        if((s/2)&1)ans+=1<<i; //小于j和大于j的都记录了所以个数要除以2
    }
    cout<<ans<<endl;
    return 0;
}