usaco 2017 February platinum

1.一条路,两边都是一个1到n的全排列,可以把其中一个全排列的起始位置改变(比如123可以变成231或者312)

然后把相同的数连起来,求小交叉数。

先算一下交叉数,然后直接一步步移动,O1更新一下状态就可以了。注意两边都要算过去。

#include<iostream>
#include<cstdio>
#define ll long long
#define N 131072
using namespace std;
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 T[N*2+5];
int n;
int s2[N];
int s[N],pos[N];
int pos2[N];
int up[N],down[N];
ll tot=0,tot2;
ll minn;

void renew(int x,int ad)
{
    T[x+=N]=ad;
    for(x>>=1;x;x>>=1)
    {
        T[x]=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+=T[l+1];
        if( r&1) sum+=T[r-1];
    }
    return sum;
}

int main()
{
    freopen("mincross.in","r",stdin);
    freopen("mincross.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
    {
        s[i]=read();
        pos2[s[i]]=i;
    }
    for(int i=1;i<=n;i++)
    {
        s2[i]=read();
        pos[s2[i]]=i;
    }
    for(int i=1;i<=n;i++)
    {
        up[i]=query(1,pos[s[i]]);
        down[i]=i-1-up[i];
        renew(pos[s[i]],1);
        tot+=(ll)down[i];
    }minn=tot2=tot;
    for(int i=n;i>1;i--)
    {
        tot-=n-pos[s[i]];tot+=pos[s[i]]-1;
        tot2-=n-pos2[s2[i]];tot2+=pos2[s2[i]]-1;
        minn=min(minn,min(tot,tot2));
    }
    cout<<minn;
    return 0;
}

 

2.有一条路,路两边都有一个随意顺序的1-n n个点。如果|a-b|<=4 那么这两个点可以连边。

现在要让所有边边两两不交叉,求最多可以连多少边。

有一道金组的一样的题目 n<=1000  直接dp即可

这道题则加大了数据 n<=100000 

所以优化一下,边最多9*n条,我们可以在左边从上到下做,在右边用一个线段树第i个位置表示只和1-i部分的点连线,最多可以连几条。

那么每次用每条边更新一下答案就行了。复杂度nlogn*9

#include<iostream>
#include<cstdio>
#define ll long long
#define N 131072
using namespace std;
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[N],pos[N];
int f[N];
int T[N*2+5];

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;
}

int main()
{
    freopen("nocross.in","r",stdin);
    freopen("nocross.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
    {
        s[i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        int x=read();pos[x]=i;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=max(1,s[i]-4);j<=min(n,s[i]+4);j++)
        {
            f[pos[j]]=max(f[pos[j]],query(1,pos[j]-1)+1);
        }
        for(int j=max(1,s[i]-4);j<=min(n,s[i]+4);j++)
             renew(pos[j],f[pos[j]]);
    }
    printf("%d\n",query(1,n));
    return 0;
}

3.还是这样一条路,路两旁还是1到n的全排列,这时候给定k 只有|a-b|<=k a和b才是友善的。

现在左右两边相同数字连边,求不友善的交叉的个数。n,k<=100000

查交叉个数可以用一个权值线段树在nlogn内查,这道题也可以用一个二维线段树做....

但是这显然太复杂了,我们考虑用cdq分治来解决这个问题。

基本思路:按照1-n的顺序,插入点,每次插入一个点m,就查一下m+k+1的答案。

所以把询问和插入节点按照时间戳进行cdq分治

每次计算答案时,将左边的插入操作和右边的查询操作按照左边的出现顺序排序,并且用线段树的做法查询答案就行了。

所以正着做一次反着做一次....复杂度nlog^2n

#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
#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,k;

ll ans=0;
int T[N*2+5];

struct cow{
    int x,y,k;
}s[100005],q[200005],a[200005];

bool cmp(cow x,cow y){return x.x<y.x||(x.x==y.x&&x.k<y.k);}

void renew(int x,int ad)
{
    //cout<<"renew"<<x<<" "<<ad<<endl;
    T[x+=N]=ad;
    for(x>>=1;x;x>>=1) T[x]=T[x<<1]+T[(x<<1)+1];
}

void query(int l,int r)
{
//    cout<<"query"<<l<<" "<<r<<endl;
    for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)
    {
        if(~l&1) ans+=T[l+1];
        if( r&1) ans+=T[r-1];    
    }
}

void solve(int l,int r)
{
    int mid=(l+r)/2,m=0;
    for(int i=l;i<=mid;i++)   if(!q[i].k)a[++m]=q[i];
    for(int i=mid+1;i<=r;i++) if( q[i].k)a[++m]=q[i];
    sort(a+1,a+m+1,cmp);
    for(int i=1;i<=m;i++)
    if(a[i].k) query(a[i].y,n);    
    else renew(a[i].y,1);
    for(int i=1;i<=m;i++) if(!a[i].k) renew(a[i].y,0);
}

void work(int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)/2;
        work(l,mid);work(mid+1,r);    
        solve(l,r);
    }
}

int main()
{
    freopen("friendcross.in","r",stdin);
    freopen("friendcross.out","w",stdout);
    n=read();k=read();
    for(int i=1;i<=n;i++)
        s[read()].x=i;
    for(int i=1;i<=n;i++)
        s[read()].y=i;
    for(int i=1;i<=n-k-1;i++)
        q[(i<<1)-1]=s[i],q[i<<1]=s[i+k+1];    
    for(int i=1;i<=n-k+1;i++) q[i<<1].k=1;
    work(1,(n-k-1)<<1);
    for(int i=n,j=1;i>=k+2;--i,++j)
        q[(j<<1)-1]=s[i],q[j<<1]=s[i-k-1];    
    for(int i=1;i<=n-k+1;i++) q[i<<1].k=1;
    work(1,(n-k-1)<<1);
    cout<<ans;
    return 0;
}

 感想:怎么都有线段树。。???

posted @ 2017-02-21 20:26  FallDream  阅读(488)  评论(1编辑  收藏  举报