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; }
感想:怎么都有线段树。。???
FallDream代表秋之国向您问好!
欢迎您来我的博客www.cnblogs.com/FallDream