JOI-2016/17 春季合宿 切题记

        17年的合宿好难啊。。。感觉是我做过的最难的一套题(没有之一)了。。。但是可能也是价值最高的?


Day1:


        T1 Cultivation:给你一个H*W的网格,有N<=300棵仙人掌。每一年可以选择一个方向(例如向上),使得每一棵仙人掌的上面都长出一棵仙人掌(如果原来就有就不变),求最少的操作次数使得每个格子都有一棵仙人掌。

        考虑将上下和左右分开来考虑。对于一维的情况,求出A,B,C,分别表示最左边的点向左的距离,相邻两个点的最大距离,最右边的点向右的距离,则答案为max(A+C,B)。

       现在考虑将向左移动和向右移动都变为向右移动,然后再将H*W的网格整体向右移动。考虑枚举向右移动的长度,注意到N棵仙人掌将所有的列分成了2N段,对于每一段都求出独立的A,B,C,显然答案为当前H*W的网格包含的所有段的max(max{A}+max{C},max{B})。用单调队列实现即可。

      另外实际上只需要考虑O(N^2)个向右移动的长度。有三种情况:

          1.最小的将整个网络填满的长度

          2.对于两棵满足横坐标x

          3.对于任意两棵仙人掌,y-x+W-1。

      复杂度O(N^3)。需要注意常数。

AC代码如下:

#include
#define ll long long
#define N 609
#define M 200009
using namespace std;
 
int H,W,n,m,cnt,len[M],p[N],q[N],A[N],B[N],C[N],ord[N];
bool bo[N][N]; ll ans=1ll<<60;
struct node{ ll x; int y; }a[N],b[N],c[N];
bool cmpx(node u,node v){ return u.x=a[c[q[tail]].y]; tail--);
		q[++tail]=x;
	}
	int top(){ return c[q[head]].y; }
}f,g,h;
void add(int x,int y){
	bo[x][y]=1;
	int i,last=-1;
	for (i=1; i<=n; i++) if (bo[x][y=ord[i]]){
		if (last==-1){ A[x]=a[y].y-1; B[x]=0; }
		else B[x]=max(B[x],a[y].y-last-1);
		last=a[y].y;
	}
	C[x]=W-last;
}
int solve(int lim){
	int i,j,k,ans=W<<1;
	for (i=k=1,j=n+1; k<=m; k++)
		c[k]=(i<=n && b[i].x<=b[j].x || j>m?b[i++]:b[j++]);
	f.clr(); g.clr(); h.clr();
	for (i=j=1; i<=m && c[i].x+H<=c[m].x; i++){
		f.pop(i); g.pop(i); h.pop(i);
		for (; j<=m && c[i].x+H>c[j].x; j++){
			f.ins(j,A); g.ins(j,B); h.ins(j,C);
		}
		ans=min(ans,max(A[f.top()]+C[h.top()],B[g.top()]));
	}
	return ans;
}
int main(){
	scanf("%d%d%d",&H,&W,&n); m=n<<1;
	int i,j;
	for (i=1; i<=n; i++) scanf("%lld%d",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmpx);
	len[cnt=1]=a[1].x-1+H-a[n].x;
	for (i=1; i=len[1])
				len[++cnt]=a[i].x+H-a[j].x-1;
			if (ilen[1])
				len[++cnt]=a[j].x-a[i].x-1;
		}
	sort(len+1,len+cnt+1);
	for (i=1; i<=n; i++) ord[i]=i;
	sort(ord+1,ord+n+1,cmpy);
	for (i=1; i<=n; i++) p[i]=q[i]=1;
	for (i=1; i<=cnt; i++) if (i==1 || len[i]>len[i-1]){
		for (j=1; j<=n; j++){
			for (; p[j]<=n && a[j].x+len[i]>=a[p[j]].x; p[j]++)
				if (a[j].x<=a[p[j]].x) add(p[j],j);
			for (; q[j]<=n && a[j].x+len[i]+1>=a[q[j]].x; q[j]++)
				if (a[j].x

 

       T2 Port Facility:给定n个货物的进栈和出栈时间和2个栈,问有多少种装货的方式。

        考虑给所有不能同时在同一个栈内的物体连边,然后先二分图染色判断合法,答案就是2^连通块个数。

       连边方法有很多种。我的方法是按左端点排序后用set维护当前存在的货物的右端点,然后加入一个新的货物时在set内查询和它相交的点,只要所有的点和左右两个点连0边,然后最左边的点和它连1边。如果一个点和左右两边的点都相连就从set中删去。复杂度O(NlogN)。

AC代码如下:

#include
#define inf 1000000000
#define N 2000009
using namespace std;
 
int n,tot,tp,q[N],a[N],b[N],fst[N],pnt[N<<3],len[N<<3],nxt[N<<3];
int lf[N],rg[N],vis[N];
set S,T; set:: iterator it;
void add(int x,int y,int z){
    pnt[++tot]=y; len[tot]=z; nxt[tot]=fst[x]; fst[x]=tot;
}
void ins(int x,int y,int z){
    x=b[x]; y=b[y];
    add(x,y,z); add(y,x,z);
}
void dfs(int x,int t){
    if (vis[x]){
        if (vis[x]!=t){ puts("0"); exit(0); }
        return;
    }
    vis[x]=t;
    int i;
    for (i=fst[x]; i; i=nxt[i]) dfs(pnt[i],t^len[i]);
}
int main(){
    scanf("%d",&n);
    int i,k,x,y;
    for (i=1; i<=n; i++){
        scanf("%d%d",&x,&y);
        a[x]=y; b[y]=i;
    }
    S.insert(-inf); S.insert(inf);
    T.insert(-inf); T.insert(inf);
    for (x=1; x<=(n<<1); x++) if (y=a[x]){
        i=b[y];
        it=S.upper_bound(y);
        rg[y]=*it; it--; lf[y]=*it;
        if (*it>x) ins(y,*it,3);
        it=T.upper_bound(y); it--;
        for (; *it>x; it--){
            k=*it;
            if (lf[k]>x){
                ins(k,lf[k],0); lf[k]=-inf;
            }
            if (rg[k]


        T3 Sparklers:有n个手中拿着烟花人站成一排,第k个人手上有一束燃着的烟花,烟花Ts后熄灭,一个人需要在Ts(含Ts)内将它传递给下一个人。已知人的奔跑速度v,求T的最小值使得所有人手中的烟花都能燃烧。

        首先二分答案。考虑如何check。考虑到如果某一次结束后i-j的所有人手中的烟花都燃烧过了,那么这时手中烟花正在燃烧的人可能在的位置是[a[j]-T*(j-i),a[i]+T*(j-i)],也就是如果a[j]-a[i]<=2*T*(j-i),那么区间(i,j)满足条件。因此就是问能否从(k,k)走到(1,n)使得所有经过的区间都满足条件。

        令b[i]=a[i]-i*T,(i,j)满足条件转化为b[i]>=b[j]。考虑当前正在(x,y),如果存在i满足min{b[i~x]}>=b[y]且b[i]>b[x]那么显然可以贪心走到(i,y)。y亦然。当不能走的时候,如果依然存在b[i](i

AC代码如下:

#include
#define ll long long
#define inf 1000000000000000000ll
#define N 100009
using namespace std;
 
int n,m,sta,tp,q[N],a0[N],lg2[N],lf[N],rg[N]; ll a[N];
struct node{ ll x; int y; }f[17][N],g[17][N];
bool operator <(node u,node v){ return u.xy) return (node){inf,x};
    int k=lg2[y-x+1];
    return min(f[k][x],f[k][y-(1<y) return (node){-inf,x};
    int k=lg2[y-x+1];
    return max(g[k][x],g[k][y-(1<=a[i]; tp--);
        rg[i]=q[tp]; q[++tp]=i;
    }
    for (x=y=sta; x>1 || y=a[y]) x=u;
        else if (v && getmax(y+1,v-1).x<=a[x]) y=v; else{
            if (x==1) return getmax(y+1,n).x<=a[x];
            if (y==n) return getmin(1,x-1).x>=a[y];
            u=getmin(1,x-1).y; v=getmax(y+1,n).y;
            if (a[u]a[x]) return 0;
            i=getmax(1,u).y; j=getmin(v,n).y;
            if (a[i]>=a[v]) x=i;
            else if (a[j]<=a[u]) y=j; else return 0;
        }
    }
    return 1;
}
int main(){
    scanf("%d%d%d",&n,&sta,&m);
    int i;
    for (i=1; i<=n; i++) scanf("%d",&a0[i]);
    for (i=2; i<=n; i++) lg2[i]=lg2[i>>1]+1;
    int l=0,r=(a0[n]-a0[1])/m/2+1,mid;
    //cerr<>1;
        if (check(2ll*mid*m)) r=mid; else l=mid+1;
    }
    printf("%d\n",l);
    return 0;
}


Day2:


        T1 Arranging Tickets:给n个站排成一个环,i和i+1(n和1)连边,m种乘客,(A,B,C)表示从A到B,有C个这样的乘客。你可以给每一个乘客选择两种路径之一,使得经过最多的边的经过数量最少。

        个人认为是本次joisc最难的一道题(如果考虑证明难度)。。。也是我唯一一道完全看题解的题。。。然而强如myy似乎还是轻松切了。看来我还是太菜了。

        三句话概括:一波操作,两个引理,三个结论。。。

        首先破环成链,二分答案ans。将每个乘客看成一条线段。然后考虑所有需要翻转的线段,它们的交集非空(否则考虑[a,b),[c,d),a

        剩下的只需要考虑从左到右贪心然后用数据结构维护即可。

AC代码如下:

#include
#define ll long long
#define N 200009
using namespace std;
 
int n,m,fst[N],nxt[N],a[N],b[N],c[N],rst[N]; ll tg[N],icr[N];
struct cmp{
	bool operator ()(int x,int y){ return b[x],cmp> Q;
bool check(int k,ll lim,ll num){
	if (num>lim) return 0;
	int i,j,x; ll tmp;
	memset(fst,0,sizeof(fst));
	for (i=1; i<=m; i++) if (a[i]<=k && b[i]>k){
		nxt[i]=fst[a[i]]; fst[a[i]]=i; rst[i]=c[i];
	}
	while (!Q.empty()) Q.pop();
	memset(icr,0,sizeof(icr));
	for (i=1; i<=k; i++){
		for (j=fst[i]; j; j=nxt[j]) Q.push(j);
		tmp=max(0ll,tg[i]+num-lim)+1>>1;
		while (tmp && !Q.empty()){
			x=Q.top(); Q.pop();
			if (rst[x]<=tmp){
				tmp-=rst[x]; num-=rst[x]<<1;
				icr[a[x]]-=rst[x]; icr[b[x]]+=rst[x]<<1;
			} else{
				rst[x]-=tmp; num-=tmp<<1; Q.push(x);
				icr[a[x]]-=tmp; icr[b[x]]+=tmp<<1; tmp=0;
			}
		}
		if (tmp) return 0;
	}
	for (i=1; i<=n; i++){
		icr[i]+=icr[i-1];
		if (icr[i]+tg[i]>lim) return 0;
	}
	return 1;
}
int main(){
	scanf("%d%d",&n,&m);
	int i,j,k;
	for (i=1; i<=m; i++)
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
	for (i=1; i<=m; i++){
		if (a[i]>b[i]) swap(a[i],b[i]);
		tg[a[i]]+=c[i]; tg[b[i]]-=c[i];
	}
	for (i=2,j=k=1; itg[j]) j=i; if (tg[i]==tg[j]) k=i;
	}
	ll l=0,r=tg[j],mid;
	while (l>1;
		if (check(j,mid,tg[j]-mid) || check(j,mid,tg[j]-mid+1)
		|| check(k,mid,tg[k]-mid) || check(k,mid,tg[k]-mid+1))
			r=mid; else l=mid+1;
	}
	printf("%lld\n",l);
}

        T2 Broken Device:通信题。压缩程序需要发送一个60位二进制数,其中有150位的编码空间,指定的40位只能为0。解码程序只知道这150位(不知道指定了哪40位)。

       3位一段。对于某三位,如果没有坏点,我们让它传递至少2位信息;如果只有1个坏点,我们让它传递至少1位信息。简单构造即可。

AC代码如下:

#include
#include"Broken_device_lib.h"
#define ll long long
#define N 159
#define ad(x) a[len++]=x;
using namespace std;
 
int len,a[N],b[N],s[N];
void Anna(int n,ll x,int m,int p[]){
    int i,j;
    for (i=0; i<60; i++,x>>=1) a[i]=x&1;
    memset(b,0,sizeof(b));
    for (i=0; i=0; i--) s[i]=s[i+1]+b[i];
    for (i=j=0; j<60 && i=60){
            for (i=59; i>=0; i--) ans=ans<<1|a[i]; return ans;
        }
    }
}

       T3 Railway Trip:(转化后变为)给你n个数,每个点向它两边第一个>=它的数连边,m次询问x->y的最短路。

       啊这不是集训队作业(的加强版)吗。。。然后发现我做那道作业的办法并不能简单用来做这道题。

       考虑倍增f[i][j],g[i][j]表示i走2^j步能到达的最左/最右的点的位置。然后询问先贪心走x,然后贪心走y即可。

AC代码如下:

#include
#define N 100009
using namespace std;
 
int n,m,a[N],q[N],f[N][17],g[N][17];
int main(){
	scanf("%d%*d%d",&n,&m);
	int i,j,l,r,x,y,u,v,ans;
	for (i=1; i<=n; i++) scanf("%d",&a[i]);
	q[j=1]=1;
	for (i=2; i<=n; i++){
		for (; j && a[i]>a[q[j]]; j--);
		f[i][0]=q[j]; q[++j]=i;
	}
	q[j=1]=n;
	for (i=n-1; i; i--){
		for (; j && a[i]>a[q[j]]; j--);
		g[i][0]=q[j]; q[++j]=i;
	}
	f[1][0]=1; g[n][0]=n;
	for (j=1; j<=16; j++)
		for (i=1; i<=n; i++){
			f[i][j]=min(f[f[i][j-1]][j-1],f[g[i][j-1]][j-1]);
			g[i][j]=max(g[f[i][j-1]][j-1],g[g[i][j-1]][j-1]);
		}
	while (m--){
		scanf("%d%d",&x,&y); if (x>y) swap(x,y);
		l=r=x; ans=0;
		for (i=16; i>=0; i--){
			u=min(f[l][i],f[r][i]); v=max(g[l][i],g[r][i]);
			if (v=0; i--){
			u=min(f[l][i],f[r][i]); v=max(g[l][i],g[r][i]);
			if (u>x){ l=u; r=v; ans+=1<


Day3:


        T1 Long Distance Coach:有一辆车从0行驶到X,其中有M个补给站a1~an。给定T,有N位乘客需要在Di+kT(k为整数)的时间喝一个单位水。司机需要在kT的时间喝水。Di互不相同。当车位于起点或者补给站时可以补充车上的水。如果某个时刻乘客想要喝水但是车上没有了,那么他会下车,你需要付出Ci的代价。补充一个单位水的代价为W。求最小的总代价(司机不能下车,乘客想喝水时不会位于补给站)。

       考虑倒着dp。(选择一个人表示让他走到终点;不选择即中途下车)令f[i]表示选择了第i个人的最小总代价,g[i]表示不选择第i个人的总代价。把每个长度为T的时间看成一段,第i段中第j和j+1个人之间有个补给站,那么考虑两个人x<=j

       这样就可以dp了。显然f[i]=max(f[i+1],g[i+1])+选择i的代价,然后g的转移方程可以看成是若干条直线在i点的最小值,维护一下这个凸壳即可。

AC代码如下:

#include
#define ll long long
#define N 200009
using namespace std;
 
int n,m,w,tp; ll L,T,c[N],p[N],b[N],f[N],g[N],s[N];
struct node{ ll x,y; }a[N],q[N];
bool cmpT(ll x,ll y){ return x%T1 && sig(q[tp].y-q[tp-1].y,q[tp].x-q[tp-1].x,i,1)>=0; tp--);
		f[i]=min(f[i+1],g[i+1])+b[i];
		g[i]=q[tp].y-q[tp].x*i-s[i-1];
		u=(node){c[i],min(f[i]+c[i]*i+s[i-1],q[tp].y-q[tp].x*i+c[i]*i)};
		for (; tp && c[i]<=q[tp].x; tp--);
		for (; tp>1 && sig(u.y-q[tp].y,u.x-q[tp].x,q[tp].y-q[tp-1].y,q[tp].x-q[tp-1].x)<=0; tp--);
		q[++tp]=u;
	}
	printf("%lld\n",min(f[1],g[1])+(L/T+1)*w);
	return 0;
}

        T2 Long Mansion:有n个房间,每个房间有若干钥匙。i个i+1之间的门需要特定的钥匙。多次询问x,y表示能否从x走到y。

        用[Li,Ri]表示从i出发最远能走到的位置。预处理f[i],g[i]表示i左侧第一个能打开i号门的房间,g[i]表示右侧。考虑从左到右求。若Ri-1>=i,显然i最远只能走到Ri。如果i能走到i-1显然[Li,Ri]=[Li-1,Ri-1],否则只能向右走,二分即可。否则只需要贪心每次向两边走即可。因为没走两次右端点必然拓展1,复杂度O(NlogN)。

AC代码如下:

#include
#define N 500009
using namespace std;
 
int n,m,tot,fst[N],pnt[N],nxt[N],a[N],last[N],lg2[N],f[19][N],g[19][N],lf[N],rg[N];
void add(int x,int y){
    pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
int getmin(int x,int y){
    if (x>y) return n+1;
    int k=lg2[y-x+1];
    return min(f[k][x],f[k][y-(1<y) return 0;
    int k=lg2[y-x+1];
    return max(g[k][x],g[k][y-(1<>1]+1;
    for (i=1; i<=18; i++)
        for (j=1; j=i){
        l=i; r=rg[i-1];
        while (l>1;
            if (getmin(i,mid-1)>=i) l=mid; else r=mid-1;
        }
        rg[i]=l;
        if (g[0][i-1]<=l){
            lf[i]=lf[i-1]; rg[i]=rg[i-1];
        } else lf[i]=i;
    } else{
        l=1; r=i;
        while (l>1;
            if (getmax(mid,i-1)<=i) r=mid; else l=mid+1;
        }
        lf[i]=l; rg[i]=i;
        while (1){
            l=rg[i]; r=n;
            while (l>1;
                if (getmin(i,mid-1)>=lf[i]) l=mid; else r=mid-1;
            }
            if (l==rg[i]) break; else rg[i]=l;
            l=1; r=lf[i];
            while (l>1;
                if (getmax(mid,i-1)<=rg[i]) r=mid; else l=mid+1;
            }
            if (l==lf[i]) break; else lf[i]=l;
        }
    }
    scanf("%d",&m);
    while (m--){
        scanf("%d%d",&x,&y);
        puts(y>=lf[x] && y<=rg[x]?"YES":"NO");
    }
    return 0;
}

       T3 Natural Park:交互题。n<=1500,m=1500的无向图,每个点度数<=7,可以询问x,y,p[]表示只经过p[]中的点能否从x走到y,在45000次操作内还原原图。

       首先考虑一条链的情况。现在随意选择一个不再链上的点x,首先判断和链的那一侧较近(记为y)。然后每次logn二分查找位于x~y上最小的点z然后递归(x,z)(y,z),找不到则存在边x->y。

       如果是一棵树那么先将y定为树根,然后先用上述过程找到一个直接连在树上的点x,然后按照bfs序二分查找x和那个点相连。

       如果是图的话,在上述过程之后将x和当前的图中相连的那个点删去,这样最多变成7个连通图。对每个连通图先判断有没有和x相连的边然后二分。复杂度O(7N+NlogN+NlogN)。

AC代码如下:

#include
#include"park.h"
#define N 1509
using namespace std;
 
int n,tot,fst[N],pnt[N<<1],nxt[N<<1],p[N],h[N],d[N],vis[N]; bool bo[N];
void add(int x,int y){
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
bool check(int x){
	int i;
	for (i=1; i<=n; i++) p[i]=(vis[i]==1 || i==x);
	return Ask(0,x-1,p+1);
}
int calc(int x){
	int i,l=1,r=n,mid;
	while (l>1;
		memset(p,0,sizeof(p));
		for (i=1; i<=mid; i++) p[i]=(vis[i]!=2);
		for (i=mid+1; i<=n; i++) p[i]=(vis[i]==1);
		p[x]=1;
		if (Ask(0,x-1,p+1)) r=mid; else l=mid+1;
	}
	return l;
}
int bfs(int sta){
	int head=0,tail=1,i,x,y; h[1]=sta;
	memset(d,-1,sizeof(d)); d[sta]=1;
	while (head>1;
				memset(p,0,sizeof(p)); p[x]=1;
				for (j=1; j<=mid; j++) p[h[j]]=1;
				if (Ask(min(i,x)-1,max(i,x)-1,p+1)) r=mid; else l=mid+1;
			}
			l=h[l];
			bo[l]=0; Answer(min(x,l)-1,max(x,l)-1);
			add(x,l); add(l,x);
		} else
			while (cnt) bo[h[cnt--]]=0;
	}
	vis[x]=1;
}
void Detect(int T,int n0){
	n=n0; vis[1]=1;
	int i;
	for (i=2; i<=n; i++) if (!vis[i]) solve(i);
}

Day 4:


         给N+M条街道,每条街道都有一个重要度。Q次询问,每次有一个人从(x,y)出发,遇到一条比当前重要度高的街道就拐弯。问最长经过多少时间走出整个地图。

        拐弯的时候大力枚举两个方向。这样对于单次询问总的状态数是O(N)的。对于Q次询问,有高超的技巧证明总的复杂度不是O(QN)而是O(Q^0.5*N)。因此开个hash或者map记录即可。

AC代码如下:

#include
#define ll long long
#define up(x,y) (x<(y)?x=(y):0)
#define N 50009
#define M 10000003
using namespace std;
 
int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};
int m,n,cas,a[N],b[N],f[4][16][N];
struct hsh{
    int tot,fst[20000003],px[M],py[M],pz[M],nxt[M]; ll len[M];
    ll qry(int x,int y,int z){
        int k=(x*12233ll+y*666ll+z)%20000003,i;
        for (i=fst[k]; i; i=nxt[i])
            if (px[i]==x && py[i]==y && pz[i]==z) return len[i];
        return 0;
    }
    void ins(int x,int y,int z,ll t){
        int k=(x*12233ll+y*666ll+z)%20000003;
        px[++tot]=x; py[tot]=y; pz[tot]=z; len[tot]=t;
        nxt[tot]=fst[k]; fst[k]=tot;
    }
}hsh;
ll solve(int x,int y,int k){
    if (!x || !y || x>m || y>n) return 0;
    ll ans=hsh.qry(x,y,k); if (ans) return ans;
    int i,j,u,v; ll tmp=k?a[x]:b[y];
    for (i=k; i<4; i+=2){
        for (j=15,u=x+dx[i],v=y+dy[i]; j>=0; j--)
            if (f[i][j][k?v:u]<=tmp){
                u+=dx[i]*(1<=k) up(f[0][i][j],f[0][i-1][j-k]);
            if (j+k<=m+1) up(f[2][i][j],f[2][i-1][j+k]);
        }
        for (j=0; j<=n+1; j++){
            f[1][i][j]=f[1][i-1][j]; f[3][i][j]=f[3][i-1][j];
            if (j>=k) up(f[1][i][j],f[1][i-1][j-k]);
            if (j+k<=n+1) up(f[3][i][j],f[3][i-1][j+k]);
        }
    }
    while (cas--){
        scanf("%d%d",&i,&j);
        printf("%lld\n",max(solve(i,j,0),solve(i,j,1))-1);
    }
    return 0;
}

        T2 City:给你一个N<=250000的树让你给每个点一个<=2^28的编码。询问时给你两个编码,判断两者是否有祖先-后代关系,如果有给出谁是祖先。

         一个显然的想法是用括号序列。然后考虑括号序列(x,y)->(x,y-x)。我们把y-x映射到一个幂函数1.03^k中。这样莫问令z为1.03^k中最小的>=y-x的数,然后我们让它y'=x+z(相当于在x的子树内添加无用点)。然后返回(x,z)即可。

AC代码如下:

#include
#include"City_lib.h"
#define ll long long
#define N 250009
using namespace std;
 
int tot,dfsclk,fst[N],pnt[N<<1],nxt[N<<1],a[N],lf[N],rg[N];
set S; map mp;
void init(){
	int i,lim=1.03*(1<<19),cnt=0; S.clear(); mp.clear();
	for (i=0; i<=lim; i=max(i+1.,i*1.03)){
		mp[i]=cnt; a[cnt++]=i;
		S.insert(i);
	}
}
void add(int x,int y){
	x++; y++;
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x,int fa){
	int i,y; lf[x]=++dfsclk;
	for (i=fst[x]; i; i=nxt[i]){
		y=pnt[i];
		if (y!=fa) dfs(y,x);
	}
	rg[x]=dfsclk=lf[x]+(*S.lower_bound(dfsclk-lf[x]));
}
void Encode(int n,int a[],int b[]){
	int i;
	tot=0;
	for (i=0; i>9,r=a[x&511],u=y>>9,v=a[y&511];
	r+=l; v+=u;
	if (u<=l && r<=v) return 0;
		else return l<=u && v<=r?1:2;
}

        T3 Dragon2:有n<=30000,条龙属于若干部落,有一条线段AB。多次询问x,y表示所有属于x的龙向属于y的龙作射线,问有多少条射线和AB有交点。无三点共线。

        标算是O(N^1.5logN)的,同时还有一个O(N^1.5)的做法,(另外似乎N^2能过)。我的做法是O(N^(5/3))的。

        把所有部落按照龙的个数以N^(1/3)为界分类。考虑以部落x为射线起点。两头龙i->j和A,B相当相当于二者满足一个二维偏序关系,可以用一维排序二维数据结构。如果用树状数组的话,复杂度为N^(5/3)*logN,难以承受。考虑用O(N^0.5)修改,O(1)询问的分块,这样由于修改的总数是O(N),因此是O(N^1.5+N^(5/3))。

AC代码如下:

#include
#define ll long long
#define N 100009
using namespace std;
 
int n,m,pt,cnt,sz[N],last[N],nxt[N],ans1[N],ans2[N];
struct point{ int x,y; }w[N],A,B;
struct node{ point p; int pos,id; }p[N],q[N];
struct trp{ int x,y,k; bool bo; }a[N],a0[N];
point operator -(point u,point v){ return (point){u.x-v.x,u.y-v.y}; }
ll crs(point u,point v){ return (ll)u.x*v.y-(ll)u.y*v.x; }
bool cmp(node u,node v){
    return u.pos0;
}
bool cmpx(trp u,trp v){ return u.x0){
        p[i]=(node){w[i]-B,0,i};
        q[i]=(node){w[i]-A,0,i};
        p[i+n]=(node){A-w[i],1,i+n};
        q[i+n]=(node){B-w[i],1,i+n};
    } else{
        p[i]=(node){w[i]-A,1,i};
        q[i]=(node){w[i]-B,1,i};
        p[i+n]=(node){B-w[i],0,i+n};
        q[i+n]=(node){A-w[i],0,i+n};
    }
    cnt=n<<1;
    sort(p+1,p+cnt+1,cmp); sort(q+1,q+cnt+1,cmp);
    for (i=1; i<=cnt; i++){
        a[p[i].id].x=a[q[i].id].y=i; a[i].bo=(i<=n);
    }
    for (i=1; i<=cnt; i++) a0[i]=a[i];
    for (i=n; i; i--){
        nxt[i]=last[a0[i].k]; last[a0[i].k]=i;
    }
    sort(a+1,a+cnt+1,cmpx);
    int lim=pow(n,1./3); blk.init();
    for (i=1; i<=pt; i++) if (sz[i]>lim){
        memset(ans1,0,sizeof(ans1));
        memset(ans2,0,sizeof(ans2));
        blk.clr();
        for (j=1; j<=cnt; j++)
            if (a[j].bo && a[j].k==i) blk.ins(a[j].y);
            else if (a[j].k!=i){
                if (a[j].bo) ans1[a[j].k]+=blk.qry(a[j].y);
                else{
                    tmp=blk.qry(a[j].y);
                    ans1[a[j].k]+=tmp; ans2[a[j].k]+=tmp;
                }
            }
        blk.clr();
        for (j=cnt,x=0; j; j--)
            if (a[j].bo && a[j].k==i){
                x++; blk.ins(a[j].y);
            } else if (a[j].k!=i && a[j].bo)
                ans2[a[j].k]+=x-blk.qry(a[j].y);
        for (j=1; j<=pt; j++){
            hsh.add(j,i,ans1[j]);
            hsh.add(i,j,ans2[j]);
        }
    }
    for (i=1; i<=m; i++){
        if (hsh.len[i]!=-1){
            printf("%d\n",hsh.len[i]); continue;
        }
        tmp=0;
        for (x=last[hsh.px[i]]; x; x=nxt[x])
            for (y=last[hsh.py[i]]; y; y=nxt[y])
                if (a0[x].x>a0[y].x && a0[x].ya0[y].x && a0[x+n].y


by lych

2017.12.29

你可能感兴趣的:(joisc)