[3.16校内训练赛]

Posted FallDream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[3.16校内训练赛]相关的知识,希望对你有一定的参考价值。

这次一个学长出题....结果我把dij写成了大顶的,就说复杂度那么科学怎么T了.........真的丢人

-------------------------------

A.给定一个长度为n的序列,你要求出从那个位置开始连续数n个数,得到的序列最大(先比第一位,再第二位..)。n<=2000000

题解:第一眼想到的是可以把每个数拆开来计数排序+dc3后缀数组,应该可过。

但是此题还有一个非常妙的解法。

假设目前最优的开头是i,你要判断开头为j的是否更优,那么你可以找到第一位不同的位k,即s[i+k]!=s[j+k],

这时候,如果s[j+k]<[i+k],那么以i开头的比j优,i+1的也会比j+1的优.....i+k的同样比j+k更优,所以我们可以直接让j=j+k+1,继续计算即可。复杂度O(n)

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 2000000
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-) f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0; ch=getchar();}
    return x*f;
}

int n,s[2*MAXN+5];
    
int main()
{
    freopen("minecraft.in","r",stdin);
    freopen("minecraft.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
        s[i]=s[i+n]=read();
    int k,i=1,j=2;
    for(;j<=n;)
    {
        for(k=0;s[j+k]==s[i+k]&&k<n;k++);
        if(s[j+k]>s[i+k])i=(s[j+k]>s[j])?j+k:j;
        j=j+k+1;
    }
    for(int j=0;j<n;j++)printf("%d ",s[j+i]);
    return 0;
}

B.有n个数s1..sn,你要从中删除一些数,使得最后满足si=i(第i个数是i)的数尽量多。n<=100000

做法:考虑一个n^2的dp,即f[i]=max(f[j])+1,但必须满足 1)i>j   2)si-sj<=i<j  3)si>sj

但是我们可以发现,如果满足了2和3两个条件,那么第一个条件一定满足。

又因为si-sj<=i-j   -> si-i<=sj-j 所以我们把si-i和si其中的一个排序,并且把另一个离散之后存到线段树里加速dp就好啦。

复杂度nlogn

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 131072
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-) f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0; ch=getchar();}
    return x*f;
}

int n;
int s[100005],f[100005],l[100005],p[100005],T[N*2+5],l2[100005];

void renew(int x,int ad)
{
    x+=N;T[x]=max(T[x],ad);
    for(x>>=1;x;x>>=1)T[x]=max(T[x<<1],T[x<<1|1]);
}

int query(int l,int r)
{
    int sum=0;
    for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
    {
        if(~l&1)sum=max(sum,T[l+1]);
        if( r&1)sum=max(sum,T[r-1]);    
    }
    return sum;
}

bool cmp(int x,int y)
{
    return s[x]<s[y]||(s[x]==s[y]&&p[x]<p[y]);
}

int main()
{
    freopen("fivethree.in","r",stdin);
    freopen("fivethree.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)s[i]=read();
    for(int i=1;i<=n;i++)l2[i]=p[i]=s[i]-i;
    sort(l2+1,l2+n+1);int j=1;
    for(int i=2;i<=n;i++)if(l2[i]!=l2[i-1]) l2[++j]=l2[i];
    for(int i=1;i<=n;i++)p[i]=j-(lower_bound(l2+1,l2+j+1,p[i])-l2)+1;
    int K=0;
    for(int i=1;i<=n;i++)
        if(s[i]-i<=0) l[++K]=i;
    sort(l+1,l+K+1,cmp);
    s[0]=-2000000000;
    for(int ii=1;ii<=K;ii++)
    {
        int i=l[ii];
        f[i]=query(1,p[i])+1;
        if(s[i]!=s[l[ii+1]])
            for(int j=ii;j&&s[l[j]]==s[l[ii]];j--)
                renew(p[l[j]],f[l[j]]);
    }    
    printf("%d\n",query(1,n));
    return 0;
}

C.有一个n个点m条边的图,有边权,你要从第1个点到第n个点,但是你必须先到2...k+1这些点去搞事情。特殊地,有一些限制(a,b),表示必须在a搞完事情之后才能在b搞事情,求搞完所有事情到达第n个点至少要走的距离。n<=20000,m<=200000,k<=20

题解:很容易发现其实和图没什么关系,而且k的范围比较小,考虑壮压dp。

先用dij预处理出两两之间和每个点到起点终点的距离

然后用f[i][j]表示最后走到第i个点,状态是j的最小距离,转移即可。状态数最多2^20*20≈20000000,转移复杂度20,但实际并没有这么多状态,并且题目有2s,所以还是比较科学的。

交了一个大顶堆,真的丢人

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define INF 2100000000
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-) f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0; ch=getchar();}
    return x*f;
}

int n,m,K,cnt=0;
int p[25],l[25],to,v[25],rk[1000005],num[1000005];
int dis[25][25],d[20005],head[20005];
int f[22][1100000];
bool mark[20005];
struct edge{
    int to,next,w;
}e[400005];
struct node{int x,f;};
class cmp{public:bool operator()(node a,node b){return a.f>b.f;}};
priority_queue<node,vector<node>,cmp>q;

void ins(int f,int t,int w)
{
    e[++cnt].next=head[f];head[f]=cnt;
    e[cnt].to=t;e[cnt].w=w;
}

void work(int num,int from)
{
    while(!q.empty())q.pop(); 
    memset(d,0x3f3f3f,sizeof(d));
    memset(mark,0,sizeof(mark));
    d[from]=0;q.push((node){from,0});
    while(!q.empty())
    {
        int u=q.top().x;q.pop();if(mark[u])continue;mark[u]=1;
        for(int i=head[u];i;i=e[i].next)
            if(d[u]+e[i].w<d[e[i].to])
            {
                d[e[i].to]=d[u]+e[i].w; 
                q.push((node){e[i].to,d[e[i].to]});        
            }    
    }
    for(int i=1;i<=K;i++)dis[num][i]=d[i+1];
    dis[num][K+1]=d[n];
}

int main()
{
    freopen("revenge.in","r",stdin);
    freopen("revenge.out","w",stdout);
    n=read();m=read();K=read();to=1<<K;
    p[1]=1;for(int i=2;i<=K;i++)p[i]=p[i-1]<<1;
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read(),w=read();
        ins(u,v,w);ins(v,u,w);
    }
    m=read();
    for(int i=1;i<=m;i++)
    {int u=read(),v=read();l[v-1]|=p[u-1];}
    work(0,1);if(K==0)return 0*printf("%d",dis[0][K+1]);
    for(int i=1;i<=K;i++)
        work(i,i+1);
    for(int i=1;i<=K;i++)
        for(int j=0;j<to;j++)
            f[i][j]=INF;
    for(int i=1;i<=K;i++) if(!l[i])
        f[i][p[i]]=dis[0][i];
    for(int i=1;i<to;i++)
    {
        for(int j=1;j<=K;j++)
            if(f[j][i]<INF)
                for(int k=1;k<=K;k++)
                    if((l[k]&i)==l[k]&&(!(i&p[k])))
                    {
                        int into=i|p[k];
                        f[k][into]=min(f[k][into],f[j][i]+dis[j][k]);
                    }
    }
    int ans=INF;
    for(int i=1;i<=K;i++)ans=min(ans,f[i][to-1]+dis[i][K+1]);
    cout<<ans;
    return 0;
}

D.有n个数,你可以任选一个区间,并且用其中的任意一个数与这个区间的次大值异或起来,求最大异或值。n<=50000

题解:这道题是最好想出来的.....

对于每个数,往两边二分到第一个比它大的数,再向两边二分到第二个比它大的数。

假设序列是1 3 4 2 6 1 8,那么对于数2,它二分到的四个点分别是3,4,6,8,假设称为l2,l1,r1,r2

那么在区间(l1,r2)和(l2,r1)内,这个数显然都是次大值。然后我们对于每个查询到的区间都在可持久化TRIE树上查询一个和它异或最大的值,更新一下答案。

二分时候可以用线段树来check,在可持久化TRIE上查询是log,所以复杂度是nlog^2n

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 2000005
#define N 65536
#define INF 2100000000
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-) f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0; ch=getchar();}
    return x*f;
}

int s[50005];
struct TRIE{
    int son[2];
    int size;
}T[MAXN];
int t[N*2+5],c[35],rt[50005],cnt=0,n,ans=0;

void renew(int x,int ad)
{
    t[x+=N]=ad;
    for(x>>=1;x;x>>=1)t[x]=max(t[x<<1],t[x<<1|1]);
}

int query(int l,int r)
{
    int sum=0;
    for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
    {
        if(~l&1)sum=max(sum,t[l+1]);
        if( r&1)sum=max(sum,t[r-1]);    
    }
    return sum;
}

void ins(int num)
{
    memset(c,0,sizeof(c));rt[num]=++cnt;
    for(int x=s[num],len=0;x;c[++len]=(x&1),x>>=1);
    for(int i=30,x=rt[num-1],nx=rt[num];i;i--)
    {
        T[nx].son[c[i]]=++cnt;T[nx].son[!c[i]]=T[x].son[!c[i]];
        x=T[x].son[c[i]];nx=T[nx].son[c[i]];
        T[nx].size=T[x].size+1;
    }
}

void calc(int l,int r,int xx)
{
    if(l>r)return;
    memset(c,0,sizeof(c));for(int x=xx,len=0;x;c[++len]=(x&1),x>>=1);
    int sum=0;
    for(int i=rt[l-1],j=rt[r],k=30,l=1<<29;k;k--,l>>=1)
    {
        c[k]=!c[k];
        int to=(T[T[j].son[c[k]]].size-T[T[i].son[c[k]]].size>0)?c[k]:1-c[k];
        if(to)sum|=l;j=T[j].son[to];i=T[i].son[to];    
    //    cout<<i<<" "<<j<<" "<<to<<" "<<sum<<endl;
    }
    ans=max(ans,sum^xx);
}

int get(int rr,int xx,int spec)
{
    int l=1,mid,r=--rr,q=rr+1;
    if(!rr)return spec;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(query(mid,rr)<xx)q=mid,r=mid-1;
        else l=mid+1;
    }
    return q;
}

int get2(int ll,int xx,int what)
{
    int l=++ll,r=n,mid,q=ll-1;
    if(ll>n)return what;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(query(ll,mid)<xx)q=mid,l=mid+1;
        else r=mid-1;    
    }
    return q;
}

int main()
{
    freopen("lock.in","r",stdin);
    freopen("lock.out","w",stdout);
    n=read();int mx=0;
    for(int i=1;i<=n;i++)
        {s[i]=read();ins(i);renew(i,s[i]);mx=max(mx,s[i]);}
    for(int i=1;i<=n;i++)
    {
        if(s[i]==mx)continue;
        int l1=get(i,s[i],2)-1,l2=get(l1,s[i],l1);
        int r1=get2(i,s[i],n-1)+1,r2=get2(r1,s[i],r1);
    //    cout<<i<<" "<<l1<<" "<<l2<<" "<<r1<<" "<<r2<<"!!!!!!!"<<endl;
        calc(l2,min(r1+1,n),s[i]);calc(max(0,l1-1),r2,s[i]);
    }
    cout<<ans;
    return 0;
}

 

以上是关于[3.16校内训练赛]的主要内容,如果未能解决你的问题,请参考以下文章

[3.3校内训练赛]

[3.9校内训练赛]

2017-4-7校内训练

两所大学中的智能车竞赛校内赛

校内第二次天梯赛总结

寒假训练计划