AtCoder Regular Contest 127

A - Leading 1s

签到,常用的计数方法,枚举至少有几个前导1,符合条件的个数算1的贡献即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=18;
ll n,p0[N],p1[N];
int main() {
    p0[0]=1;
    for(int i=1;i<N;i++) p0[i]=p0[i-1]*10;
    p1[1]=1;
    for(int i=2;i<N;i++) p1[i]=p1[i-1]*10+1;
    cin>>n;
    ll ans=0;
    for(int x=1;x<N && p1[x]<=n;x++){
        for(int y=0;y<N && p1[x]*p0[y]<=n;y++){
            ans+=min(p0[y],n-(p1[x]*p0[y])+1);
        }
    }
    cout<<ans<<endl;
    return 0;
}

B - Ternary Strings

因为有字典序的要求,故把每个串当成一个三进制数考虑,那么就是要求一个最大值最小的合法构造。而这个最大值肯定在开头为2的那一组,并且至少是\(2\times 3^{L-1}+N-1\),即直接把开头为2的N个数的后面填上\(0,...,N-1\)。然后发现这样是可以补成合法的,即对填的每个位置的数,在对应的开头为0/1的位置进行一轮置换。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,L;
char ans[N][20];
int main() {
    cin>>n>>L;
    int m=n*3;
    for(int i=0;i<m;i++) ans[i][0]='0'+i/n;
    for(int i=0;i<n;i++){
        int x=i;
        for(int j=L-1;j;j--){
            int t=x%3;
            ans[i+n+n][j]='0'+t;
            ans[i+n][j]='0'+(t+1)%3;
            ans[i][j]='0'+(t+2)%3;
            x/=3;
        }
    }
    for(int i=0;i<m;i++) printf("%s\n",ans[i]);
    return 0;
}

C - Binary Strings

考虑按照字典序从小到大枚举,发现是一个二叉树的结构;所以沿着二叉树走,每次判断停下或者往左/右子树走,并维护是当前子树内的第几个,往左-1,往右-当前位,效率经过分析应该是线性的。
然后原本的代码里用了n次strlen(a),原本听说不要用还以为只是常数大,没想到是\(O(n)\)的。没发现的时候怀疑人生了半天,以为效率分析错了,但改成线段树还是TLE,自闭了很久。。。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
void print(int* a,int m){
    for(int i=0;i<m;i++) putchar('0'+a[i]);
    puts("");
}
int n,a[N],ans[N],cnt;
void sub(){
    int q=0;
    while(!a[q] && q<n) q++;
    for(int i=0;i<q;i++) a[i]=1,cnt++;
    a[q]=0,cnt--;
}
bool chk(){
    for(int i=0;i<n;i++) if(a[i]) return 0;
    return 1;
}
char b[N];
int main() {
    //freopen("1.in","r",stdin);
   // puts("!");
    cin>>n;
    scanf("%s",b);
    int m=strlen(b);
    reverse(b,b+m);
  //  puts("?");
    for(int i=0;i<n;i++){
    	//cout<<i<<endl;
        if(i<m) a[i]=b[i]-'0';
        else a[i]=0;
        if(a[i]) cnt++;
    }
   // puts("!!");
    ans[0]=1;
    sub();
   // puts("??");
    for(int i=1;i<=n;i++){
       // cout<<"i="<<i<<endl;
        //print(ans,i);
        //print(a,n);

        if(chk()){
            print(ans,i);
            return 0;
        }
        int len=n-i;
        if(!a[len]){
            ans[i]=0;
            sub();
            continue;
        }
        a[len]=0; cnt--;
        ans[i]=1;
    }

    return 0;
}

D - LIS 2

难搞的地方在于取min,考虑比较\((a_i \oplus a_j,b_i \oplus b_j)\)两者的过程:是在它们第一位不一样的地方比较,取该位为0的那个。
而判断两个数在某位是否相等,可以想到异或操作,然后把这两者异或起来后,由异或运算的交换律可得等价于\((a_i \oplus b_i) \oplus (a_j \oplus b_j)\),这样就转成两个位置独立的式子的异或值,然后枚举这个第一个为1的位置,在trie树上记一些东西,再类似地查一下就行。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5,M=18;
int n,a[N],b[N],ch[N][2],cnt=1;
ll tot[N][2],suma[N][2][M],sumb[N][2][M];
int main() {
    cin>>n;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    for(int i=1;i<=n;i++){
        int z=(a[i]^b[i]);
        int u=1;
        for(int p=M-1;p>=0;p--){
            int q=(z&(1<<p))>>p,v=(a[i]&(1<<p))>>p;
            if(!ch[u][q]) ch[u][q]=++cnt;
            u=ch[u][q];
            for(int k=0;k<M;k++){
                if(a[i]&(1<<k)) suma[u][v][k]++;
                if(b[i]&(1<<k)) sumb[u][v][k]++;
            }
            tot[u][v]++;
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        //cout<<"i="<<i<<endl;
        int z=(a[i]^b[i]);
        int u=1;
        for(int p=M-1;p>=0;p--){
            int q=(z&(1<<p))>>p;
            int cur=ch[u][!q];

            int v=(a[i]&(1<<p))>>p;
            //v=(!v);
            for(int k=0;k<M;k++){
                if(a[i]&(1<<k)) ans+=(tot[cur][v]-suma[cur][v][k])*(1<<k);
                else ans+=suma[cur][v][k]*(1<<k);
            }
            //cout<<ans<<endl;
            v=(!v);
            for(int k=0;k<M;k++){
                if(b[i]&(1<<k)) ans+=(tot[cur][v]-sumb[cur][v][k])*(1<<k);
                else ans+=sumb[cur][v][k]*(1<<k);
            }
            u=ch[u][q];
            //cout<<p<<" "<<ans<<endl;
        }
        int cur=u;
        for(int v=0;v<2;v++)
            for(int k=0;k<M;k++){
                if(a[i]&(1<<k)) ans+=(tot[cur][v]-suma[cur][v][k])*(1<<k);
                else ans+=suma[cur][v][k]*(1<<k);
            }
        //cout<<"ans="<<ans<<endl;
    }
    cout<<ans/2<<endl;
    return 0;
}

E - Priority Queue

一般的思路是,考虑如何判断某个最终集合是否合法,但这样会得到非常复杂的转移,不是很可做。
从边界的角度,考虑令留下的集合最小和最大的情况(非严格定义,感性理解):最小显然可以做到删掉最大的那些元素;最大就是从小到大加入元素。然后发现这样做之后,对一个数\(v\),最小的做法使得小于它的元素最多;而最大的做法使得大于它的元素最多。
然后考虑把最终留下的元素升序排序,则对于某个合法的答案,每个元素必须要在,上面得出的对应位置的值作为上下界的区间内。(由上面的结论可以反证出来)
然后可以证明,这个条件是充分的。因为对于留下的最大的集合(删去的最小),一定可以通过修改,将本来要删掉的集合换成更大的(直接一一对应过去即可)。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=10005,P=998244353;
void inc(int& x,int y){
    x+=y;
    if(x>=P) x-=P;
    if(x<0) x+=P;
}
int sum(int x,int y){
    x+=y;
    if(x>=P) x-=P;
    if(x<0) x+=P;
    return x;
}
void mul(int& x,int y){
    x=1ll*x*y%P;
}
int prd(int x,int y){
    return 1ll*x*y%P;
}
int n,m,a[N],mx[N];
set<int>st;
int pos[N],f[N][N];
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++) mx[i]=1;
    int tot=0;
    for(int i=1;i<=n+m;i++){
        int x;
        scanf("%d",&x);
        if(x==1){
            st.insert(++tot);
        }
        else{
            set<int>:: iterator it=st.end();
            it--;
            mx[*it]=0;
            st.erase(it);
        }
    }
    for(int i=1,j=0;i<=n;i++) if(mx[i]) pos[++j]=i;//cout<<pos[j]<<" "; puts("");
    f[0][0]=1;
    for(int j=1;j<=n;j++) inc(f[0][j],f[0][j-1]);
    for(int i=1;i<=n-m;i++){
        for(int j=1;j<=pos[i];j++) f[i][j]=f[i-1][j-1];
        for(int j=1;j<=n;j++) inc(f[i][j],f[i][j-1]);
    }
    cout<<f[n-m][n]<<endl;
    return 0;
}

posted @ 2023-04-10 19:10  sz[sz]  阅读(30)  评论(0编辑  收藏  举报