2017 清北济南考前刷题Day 3 morning

 

实际得分:100+0+0=100

T1

 

 

右上角是必败态,然后推下去

发现同行全是必胜态或全是必败态,不同行必胜必败交叉

列同行

所以n,m 只要有一个是偶数,先手必胜

#include<cstdio>

using namespace std;

int main()
{
    freopen("star.in","r",stdin);
    freopen("star.out","w",stdout);
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(!n) return 0;
        if(!(n&1) || !(m&1)) puts("Yuri");
        else puts("Chito");
    }
}
View Code

 

T2

 

k=1 暴力:

可持久化trie树 求 异或最大值

#include<cstdio>
#include<iostream>
#include<algorithm>

#define N 50001

const int mod=1e9+7;

using namespace std;

int bit[31];

int a[N];

int root[N],ch[N*31][2],cnt[N*31],tot;

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void insert(int pre,int &now ,int dep,int x)
{
    if(!now) now=++tot;
    cnt[now]=cnt[pre]+1;
    if(dep<0) return;
    int p= (x&bit[dep])>0;
    ch[now][p^1]=ch[pre][p^1];
    insert(ch[pre][p],ch[now][p],dep-1,x);
}

int query(int pre,int k,int dep,int x)
{
    if(dep<0) return 0;
    int p= (x&bit[dep])>0;
    if(cnt[ch[k][p^1]]-cnt[ch[pre][p^1]]) return bit[dep]+query(ch[pre][p^1],ch[k][p^1],dep-1,x);
    return query(ch[pre][p],ch[k][p],dep-1,x);
}

int main()
{
    freopen("war.in","r",stdin);
    freopen("war.out","w",stdout);
    bit[0]=1;
    for(int i=1;i<=30;++i) bit[i]=bit[i-1]<<1;
    int n,k;
    read(n); read(k);
    for(int i=1;i<=n;++i) 
    {
        read(a[i]);
        insert(root[i-1],root[i],31,a[i]);
    }
    int ans=0;
    for(int i=1;i<=n;++i)  ans=max(ans,query(root[0],root[n],31,a[i]));
    cout<<ans%mod;
}
View Code

 

所有数不超过1023暴力:

预处理所有 i^j的结果,cnt[i]表示第i个数的个数

这样每一种 异或 值出现的次数=cnt[i]*cnt[j]

从大到小枚举,直至k个即可

#include<cstdio>
#include<iostream>
#include<algorithm>

#define N 50001

const int mod=1e9+7;

using namespace std;

int cnt[1026];

struct node
{
    int i,j,y;
}e[1024*1024+5];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

bool cmp(node p,node q)
{
    return p.y>q.y;
}

int main()
{
    freopen("war.in","r",stdin);
    freopen("war.out","w",stdout);
    int n,x; long long k;
    read(n); scanf("%I64d",&k);
    for(int i=1;i<=n;++i) read(x),cnt[x]++;
    int tot=0;
    for(int i=0;i<1024;++i)
        for(int j=i+1;j<1024;++j)
            e[++tot].i=i,e[tot].j=j,e[tot].y=i^j;
    sort(e+1,e+tot+1,cmp);
    int ans=0;
    for(int i=1;i<=tot;++i)
    {
        ans=(ans+min(k,1ll*cnt[e[i].i]*cnt[e[i].j])*e[i].y%mod)%mod;
        k-=min(k,1ll*cnt[e[i].i]*cnt[e[i].j]);
        if(!k) break;
    }
    cout<<ans;
}
View Code

 

满分做法:

二分出一个最大的t,t满>t的数至少有k个

然后查询所有异或值 >t 的数的和,最后在减去 属于前k大的数的和

二分检验与查询均在trie树上进行

 

查询>t的数的个数:

枚举n个数a[i],累积 与每一个a[i] 异或后 >t 的个数  

对于每一个a[i],设现在是第j位

如果t的第j位是0,那么累加第j位与a[i] 不同 的数的个数,trie树上的当前位置转到 第j位与a[i] 的第j位相同的位置

因为所以在第j位就分出大小的数都以累加,继续找第j位分不出大小的数

如果t的第j位是1,什么都不累加,trie树上的当前位置 转到 第j位与a[i] 不同的 位置

因为如果这一位与t的第j位相同,异或得0,一定<t,如果不同,第j位 分不出大小,继续往后走

 

累积所有>t的数的和:

还是枚举n个数a[i],累积 与每一个 a[i] 异或后>t 的数的和

累积方法思路与上面差不多

如果t的第j位是0,那么就累计 当前位置 所有的与a[i] 异或 为1的数的和,当前位置转到 与a[i]的第j位相同的位置

如果t的第j为是1,那么当前位置 转到与a[i]的第j位不同的位置

 

注意每一对合法的数会使用两次,所以累计个数 和 总和 时 注意 除以2

 

#include<cstdio>
#include<iostream>

using namespace std;

#define N 50001

const int mod=1e9+7;
const int inv=5e8+4;

long long k; int n;

int a[N],trie[N*31][2],all[N*31][31],tot=1;

int cnt[N*31];

int bit[31];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
} 

void insert(int x)
{
    int now=1,d;
    for(int i=30;i>=0;--i)
    {
        d=(x&bit[i])>0;
        if(!trie[now][d]) trie[now][d]=++tot;
        now=trie[now][d];
        cnt[now]++;
        for(int j=30;j>=0;--j) 
            if(x&bit[j]) all[now][j]++;
    }
}

int upper(int x,int t)
{
    int pos=1,d,sum=0;
    for(int i=30;i>=0;--i)
    {
        d=(x&bit[i])>0;
        if(!(t&bit[i])) sum+=cnt[trie[pos][d^1]],pos=trie[pos][d];
        else pos=trie[pos][d^1];
    //    if(t==754974719 && x==962029906) cout<<i<<' '<<sum<<' '<<trie[pos][d^1]<<'\n';
    }
    return sum;
}

int query(int x,int t)
{
    int pos=1,d,sum=0;
    for(int i=30;i>=0;--i)
    {
        d=(x&bit[i])>0;
        if(!(t&bit[i])) 
        {
            for(int j=30;j>=0;--j) 
            if(x&bit[j]) sum=(sum+1LL*(cnt[trie[pos][d^1]]-all[trie[pos][d^1]][j])*bit[j]%mod)%mod;
            else sum=(sum+1LL*all[trie[pos][d^1]][j]*bit[j]%mod)%mod;
            pos=trie[pos][d];
        }
        else pos=trie[pos][d^1];
    }
    //cout<<sum<<'\n';
    return sum;
    
}

long long check(int x)
{
    long long sum=0;
    for(int i=1;i<=n;i++) 
    {
        sum+=upper(a[i],x);
        //if(x==754974719) cout<<i<<' '<<sum<<'\n';
    }
    return sum/2;
}

int main()
{
    freopen("war.in","r",stdin);
    freopen("war.out","w",stdout);
    read(n); scanf("%I64d",&k);
    bit[0]=1;
    for(int i=1;i<=30;++i) bit[i]=bit[i-1]<<1;
    for(int i=1;i<=n;++i)
    {
        read(a[i]);
        insert(a[i]);
    //    cout<<i<<' '<<tot<<'\n';
    }
    int l=0,r=2147483647,mid,tmp=-1;
    while(l<=r)
    {
        mid=l+r>>1;
        if(check(mid)>=k) l=mid+1,tmp=mid;
        else r=mid-1;
      //  cout<<mid<<' '<<check(mid)<<'\n';
    }
    int ans=0;
    if(tmp!=-1)
    {
        long long res=k-check(tmp);
        for(int i=1;i<=n;i++) 
        {
            ans+=query(a[i],tmp),ans%=mod;
        //    cout<<ans<<'\n';
        }
        ans=1LL*ans*inv%mod;
        ans=(ans+1LL*res*(tmp+1)%mod)%mod;
    }
    else
    {
        for(int i=1;i<=n;i++) ans+=query(a[i],0),ans%=mod;
        ans=1LL*ans*inv%mod;
    }
    cout<<ans;
    //for(int i=1;i<=tot;i++) cout<<i<<' '<<cnt[i]<<'\n';
}
View Code

 

 T3

 

因为k<=10 所以线段树维护区间前10大

合并的时候,用了类似于归并排序时用的 两个指针,扫一遍即可

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

#define N 100001

int f[N<<2];

struct node
{
    int mx[11];
    
    node() { memset(mx,0,sizeof(mx));}
    
    node operator + (node p) 
    {
        node t;
        int a=1,b=1,c=1;
        while(c<=10) t.mx[c++]=mx[a]>p.mx[b] ? mx[a++] : p.mx[b++];
        return t;
    }
}tr[N<<2];

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void build(int k,int l,int r)
{
    if(l==r) { read(tr[k].mx[1]); return; }
    int mid=l+r>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    tr[k]=tr[k<<1]+tr[k<<1|1];
}

void down(int k)
{
    f[k<<1]+=f[k];
    f[k<<1|1]+=f[k];
    for(int i=1;i<=10;++i)
    {
        if(tr[k<<1].mx[i]) tr[k<<1].mx[i]+=f[k];
        if(tr[k<<1|1].mx[i]) tr[k<<1|1].mx[i]+=f[k];
    } 
    f[k]=0;
}

node query(int k,int l,int r,int opl,int opr)
{
    if(l>=opl && r<=opr) return tr[k];
    if(f[k]) down(k);
    int mid=l+r>>1;
    if(opr<=mid) return query(k<<1,l,mid,opl,opr);
    if(opl>mid) return query(k<<1|1,mid+1,r,opl,opr);
    return query(k<<1,l,mid,opl,opr)+query(k<<1|1,mid+1,r,opl,opr);
}

void change(int k,int l,int r,int opl,int opr,int w)
{
    if(l>=opl && r<=opr)
    {
        for(int i=1;i<=10;i++) 
            if(tr[k].mx[i]) tr[k].mx[i]+=w;
        f[k]+=w;
        return;
    }
    if(f[k]) down(k);
    int mid=l+r>>1;
    if(opl<=mid) change(k<<1,l,mid,opl,opr,w);
    if(opr>mid) change(k<<1|1,mid+1,r,opl,opr,w);
    tr[k]=tr[k<<1]+tr[k<<1|1];
}

int main()
{
    freopen("noname.in","r",stdin);
    freopen("noname.out","w",stdout);
    int n,m;
    read(n); read(m);
    build(1,1,n);
    int op,l,r,w;
    while(m--)
    {
        read(op); read(l); read(r); read(w); 
        if(!op) 
        {
            if(r-l+1<w) cout<<-1<<'\n';
            else cout<<query(1,1,n,l,r).mx[w]<<'\n';
        }
        else change(1,1,n,l,r,w);
    }
}
View Code

 

 错误:

将前10大全部放到一个结构体里,query时直接返回结构体

合并的时候 重载的 加号 运算符

所以 标记 不能放到 结构体里

下传标记的时候,只传前10大,但应先判断是否具有第i大

posted @ 2017-11-06 08:45  TRTTG  阅读(259)  评论(3编辑  收藏  举报