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<y的仙人掌,y-x-1

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

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

AC代码如下:

#include<bits/stdc++.h>
#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<v.x; }
bool cmpy(int x,int y){ return a[x].y<a[y].y; }
struct Q{int head,tail,q[N];void clr(){ head=1; tail=0; }void pop(int x){for (; head<=tail && q[head]<x; head++);}void ins(int x,int *a){for (; head<=tail && a[c[x].y]>=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<n; i++) len[1]=max((ll)len[1],a[i+1].x-a[i].x-1);for (i=1; i<=n; i++)for (j=1; j<=n; j++) if (i!=j){if (a[i].x+H-a[j].x-1>=len[1])len[++cnt]=a[i].x+H-a[j].x-1;if (i<j && a[j].x-a[i].x-1>len[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<a[q[j]].x) add(j+n,q[j]);}for (j=1; j<=n; j++){b[j]=(node){a[j].x,j};b[j+n]=(node){a[j].x+len[i]+1,j+n};}ans=min(ans,(ll)len[i]+solve(len[i]));}printf("%lld\n",ans);return 0;
}

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

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

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

AC代码如下:

#include<bits/stdc++.h>
#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<int> S,T; set<int>:: 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]<y){ins(k,rg[k],0); rg[k]=inf;}if (lf[k]==-inf && rg[k]==inf) q[++tp]=k;}while (tp) T.erase(q[tp--]);S.insert(y); T.insert(y);}int ans=1;for (i=1; i<=n; i++) if (!vis[i]){dfs(i,1); ans=(ans<<1)%1000000007;}printf("%d\n",ans);return 0;
}

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<x)使得b[i]<b[y],那么显然不合法(因为b[y]不可能再减小了)。这个时候就只能两边贪心尽量走。如果能走到(1,n)则合法。

AC代码如下:

#include<bits/stdc++.h>
#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.x<v.x; }
node getmin(int x,int y){if (x>y) return (node){inf,x};int k=lg2[y-x+1];return min(f[k][x],f[k][y-(1<<k)+1]);
}
node getmax(int x,int y){if (x>y) return (node){-inf,x};int k=lg2[y-x+1];return max(g[k][x],g[k][y-(1<<k)+1]);
}
bool check(ll lim){int i,j,x,y,u,v;for (i=1; i<=n; i++){a[i]=a0[i]-i*lim;f[0][i]=g[0][i]=(node){a[i],i};}//for (i=1; i<=n; i++) cerr<<a[i]+200<<' ';cerr<<endl;for (i=1; i<=16; i++)for (j=1; j<=n; j++){f[i][j]=f[i-1][j]; g[i][j]=g[i-1][j];if (j+(1<<i-1)<=n){f[i][j]=min(f[i][j],f[i-1][j+(1<<i-1)]);g[i][j]=max(g[i][j],g[i-1][j+(1<<i-1)]);}}for (i=1,tp=0; i<=n; i++){for (; tp && a[q[tp]]<=a[i]; tp--);lf[i]=q[tp]; q[++tp]=i;}for (i=n,tp=0; i; i--){for (; tp && a[q[tp]]>=a[i]; tp--);rg[i]=q[tp]; q[++tp]=i;}for (x=y=sta; x>1 || y<n;){//    cerr<<x<<' '<<y<<endl;u=lf[x]; v=rg[y];//    cerr<<" "<<u<<' '<<v<<endl;if (u && getmin(u+1,x-1).x>=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[y] || a[v]>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<<check(8*m*2)<<endl;return 0;while (l<r){mid=l+r>>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<b<c<d,那么都不翻转显然更优),考虑枚举这个非空部分中的一个点t。然后第一个结论是覆盖点t的线段中恰好有ans或ans-1条不需要翻转。第二个结论是这样的点t一定是所有点中被覆盖次数最多的。第三个结论是如果有多个满足条件的t,这个点t只需要取最左边或者最右边的那个即可。

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

AC代码如下:

#include<bits/stdc++.h>
#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]<b[y]; }
};
priority_queue<int,vector<int>,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; i<n; i++){tg[i]+=tg[i-1];if (tg[i]>tg[j]) j=i; if (tg[i]==tg[j]) k=i;}ll l=0,r=tg[j],mid;while (l<r){mid=l+r>>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<bits/stdc++.h>
#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<m; i++) b[p[i]]=1;memset(s,0,sizeof(s));for (i=n-1; i>=0; i--) s[i]=s[i+1]+b[i];for (i=j=0; j<60 && i<n; i+=3) if (s[i]==s[i+3]){if (!a[j] && !a[j+1]){Set(i,0); Set(i+1,1); Set(i+2,0);} else if (!a[j] && a[j+1]){Set(i,0); Set(i+1,1); Set(i+2,1);} else if (a[j] && !a[j+1]){Set(i,1); Set(i+1,0); Set(i+2,1);} else{Set(i,1); Set(i+1,1); Set(i+2,1);}j+=2;} else if (s[i]==s[i+3]+1){if (b[i]){if (a[j]){Set(i,0); Set(i+1,0); Set(i+2,1); j++;} else{Set(i,0); Set(i+1,1); Set(i+2,a[j+1]); j+=2;}} else if (b[i+1]){if (!a[j]){Set(i,1); Set(i+1,0); Set(i+2,0);} else{Set(i,0); Set(i+1,0); Set(i+2,1);}j++;} else{if (!a[j]){Set(i,1); Set(i+1,0); Set(i+2,0);} else{Set(i,1); Set(i+1,1); Set(i+2,0);}j++;}} else{Set(i,0); Set(i+1,0); Set(i+2,0);}while (i<n) Set(i++,0);
}
ll Bruno(int n,int p[]){int i,x; ll ans=0; len=0;for (i=0; i<n; i+=3){x=p[i]<<2|p[i+1]<<1|p[i+2];if (x==1) ad(1);if (x==2){ ad(0); ad(0); }if (x==3){ ad(0); ad(1); }if (x==4) ad(0);if (x==5){ ad(1); ad(0); }if (x==6) ad(1);if (x==7){ ad(1); ad(1); }if (len>=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<bits/stdc++.h>
#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<y){ l=u; r=v; ans|=1<<i; }}x=r; l=r=y;for (i=16; i>=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<<i; }}printf("%d\n",ans);}return 0;
}

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<y,如果不选择x而选择y,那么x可以在第i段的时候下车。用a[i]表示第i个人和i-1个人之间最早出现的补给站,那么如果有两个人x<i<=y,x最早只能在第a[i]段下车。

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

AC代码如下:

#include<bits/stdc++.h>
#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%T<y%T; }
bool cmpx(node u,node v){ return u.x<v.x; }
int sig(ll x,ll y,ll u,ll v){int flag=1;if (u<0){ x=-x; u=-u; flag=-1; }if (x<0) return -flag;ll p=x/y,q=u/v;for (; p==q; flag=-flag){x-=p*y; u-=q*v;if (!x && !u) return 0;if (!x) return -flag; if (!u) return flag;swap(x,y); swap(u,v);p=x/y; q=u/v;}if (p!=q) return flag*(p<q?-1:1);
}
int main(){scanf("%lld%d%d%d%lld",&L,&n,&m,&w,&T);int i,j; node u;for (i=1; i<=n; i++) scanf("%lld",&p[i]); p[++n]=L;for (i=1; i<=m; i++) scanf("%lld%lld",&a[i].x,&a[i].y);sort(p+1,p+n+1,cmpT);sort(a+1,a+m+1,cmpx);for (i=1; i<=m; i++) b[i]=((L-a[i].x)/T+1)*w;for (i=1; i<=m; i++) s[i]=s[i-1]+a[i].y;for (i=1; i<=m+1; i++) c[i]=(L/T+1)*w;a[m+1].x=T;for (i=j=1; i<=m+1; i++)for (; j<=n && p[j]%T<a[i].x; j++)c[i]=min(c[i],p[j]/T*w);f[m+1]=g[m+1]=0; q[tp=1]=(node){c[m+1],c[m+1]*(m+1)+s[m]};for (i=m; i; i--){for (; tp>1 && 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<bits/stdc++.h>
#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<<k)+1]);
}
int getmax(int x,int y){if (x>y) return 0;int k=lg2[y-x+1];return max(g[k][x],g[k][y-(1<<k)+1]);
}
int main(){scanf("%d",&n);int i,j,x,y;for (i=1; i<n; i++) scanf("%d",&a[i]);for (i=1; i<=n; i++){scanf("%d",&x);while (x--){scanf("%d",&y); add(i,y);}}memset(last,0,sizeof(last));for (i=1; i<n; i++){for (j=fst[i]; j; j=nxt[j]) last[pnt[j]]=i;f[0][i]=last[a[i]];}memset(last,60,sizeof(last));for (i=n-1; i; i--){for (j=fst[i+1]; j; j=nxt[j]) last[pnt[j]]=i+1;g[0][i]=last[a[i]];}for (i=2; i<n; i++) lg2[i]=lg2[i>>1]+1;for (i=1; i<=18; i++)for (j=1; j<n; j++){f[i][j]=f[i-1][j]; g[i][j]=g[i-1][j];if (j+(1<<i-1)<n){f[i][j]=min(f[i][j],f[i-1][j+(1<<i-1)]);g[i][j]=max(g[i][j],g[i-1][j+(1<<i-1)]);}}int l,r,mid;for (i=1; i<=n; i++) if (rg[i-1]>=i){l=i; r=rg[i-1];while (l<r){mid=l+r+1>>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<r){mid=l+r>>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<r){mid=l+r+1>>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<r){mid=l+r>>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<bits/stdc++.h>
#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<r){mid=l+r>>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<tail){x=h[++head]; p[x]=1;for (i=fst[x]; i; i=nxt[i]){y=pnt[i];if (bo[y] && d[y]==-1){ d[y]=d[x]+1; h[++tail]=y; }}}return tail;
}
void solve(int x){int i,j,cnt,y,l,r,mid;vis[x]=2;for (y=x; !check(x); solve(y)) y=calc(x);for (i=1; i<=n; i++) bo[i]=(vis[i]==1);for (i=1; i<=n; i++) while (bo[i]){memset(p,0,sizeof(p)); p[x]=1;cnt=bfs(i);if (Ask(min(i,x)-1,max(i,x)-1,p+1)){l=1; r=cnt;while (l<r){mid=l+r>>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);} elsewhile (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<bits/stdc++.h>
#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<<j); v+=dy[i]*(1<<j);}ans=max(ans,abs(u-x)+abs(v-y)+solve(u,v,k^1));}hsh.ins(x,y,k,ans); return ans;
}
int main(){scanf("%d%d%d",&m,&n,&cas);int i,j,k;for (i=1; i<=m; i++) scanf("%d",&a[i]);for (i=1; i<=n; i++) scanf("%d",&b[i]);a[0]=a[m+1]=b[0]=b[n+1]=1000000001;for (i=0; i<=m+1; i++) f[0][0][i]=f[2][0][i]=a[i];for (i=0; i<=n+1; i++) f[1][0][i]=f[3][0][i]=b[i];for (i=1; i<=15; i++){for (j=0,k=1<<i-1; j<=m+1; j++){f[0][i][j]=f[0][i-1][j]; f[2][i][j]=f[2][i-1][j];if (j>=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<bits/stdc++.h>
#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<int> S; map<int,int> 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<n-1; i++){add(a[i],b[i]); add(b[i],a[i]);}init(); dfs(1,0);for (i=1; i<=n; i++) Code(i-1,lf[i]<<9|mp[rg[i]-lf[i]]);
}
void InitDevice(){ init(); }
int Answer(ll x,ll y){int l=x>>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<bits/stdc++.h>
#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.pos<v.pos || u.pos==v.pos && crs(u.p,v.p)>0;
}
bool cmpx(trp u,trp v){ return u.x<v.x; }
struct blk{int pos[N],lf[N],rg[N],a[N],b[N];void clr(){ memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); }void init(){int i,j,sz=sqrt(cnt);for (i=1; i<=sz; i++){lf[i]=rg[i-1]+1; rg[i]=rg[i-1]+sz;}rg[sz]=cnt;for (i=1; i<=sz; i++)for (j=lf[i]; j<=rg[i]; j++) pos[j]=i;}void ins(int x){int i;for (i=1; i<pos[x]; i++) a[i]++;for (i=lf[pos[x]]; i<x; i++) b[i]++;}int qry(int x){ return a[pos[x]]+b[x]; }
}blk;
struct hsh{int tot,fst[20000003],px[N],py[N],len[N],nxt[N];void ins(){int x,y; scanf("%d%d",&x,&y);int k=(x*12233ll+y*666ll)%20000003;px[++tot]=x; py[tot]=y; len[tot]=-1; nxt[tot]=fst[k]; fst[k]=tot;}void add(int x,int y,int z){int i,k=(x*12233ll+y*666ll)%20000003;for (i=fst[k]; i; i=nxt[i])if (px[i]==x && py[i]==y){ len[i]=z; return; }}
}hsh;
int main(){scanf("%d%d",&n,&pt);int i,j,x,y,tmp;for (i=1; i<=n; i++){scanf("%d%d%d",&w[i].x,&w[i].y,&a[i].k);a[i+n].k=a[i].k; sz[a[i].k]++;}scanf("%d%d%d%d",&A.x,&A.y,&B.x,&B.y);scanf("%d",&m);for (i=1; i<=m; i++) hsh.ins();for (i=1; i<=n; i++) if (crs(w[i]-A,B-A)>0){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].y<a0[y].y || a0[x+n].x>a0[y].x && a0[x+n].y<a0[y].y) tmp++;printf("%d\n",tmp);}return 0;
}
by lych

2017.12.29

JOI-2016/17 春季合宿 切题记相关推荐

  1. BZOJ 4221 [JOI2012春季合宿]Kangaroo (DP)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=4221 题解 orz WYC 爆切神仙DP 首先将所有袋鼠按大小排序.考虑从前往后DP, ...

  2. JOI2017 春季合宿:Railway Trip

    自己的AC做法似乎离正解偏了十万八千里而且复杂了不少--不管怎样还是记录下来吧. 题意: 题目链接: JOISC2017 F - AtCoder JOISC2017 F - LOJ \(N\)个车站排 ...

  3. 【JOI2017春季合宿】Port Facility

    http://uoj.ac/problem/356 题解 思路和\(NOIP\)双栈排序差不多. 对于两个元素,若\(l_1<l_2<r_1<r_2\)那么它们不能在一个栈里,我们连 ...

  4. BZOJ 4388 [JOI2012春季合宿]Invitation (线段树、二叉堆、最小生成树)

    题目链接 https://www.lydsy.com/JudgeOnline/problem.php?id=4388 题解 模拟Prim算法? 原题所述的过程就是Prim算法求最大生成树的过程.于是我 ...

  5. LOJ #2731 [JOI2016春季合宿]Solitaire (DP、组合计数)

    题目链接 https://loj.ac/problem/2731 题解 首先一个很自然的思路是,设\(dp[i][j]\)表示选了前\(i\)列,第\(2\)行第\(i\)列的格子是第\(j\)个被填 ...

  6. LOJ #2733 [JOI2016春季合宿]Sandwiches (DP)

    题目链接 https://loj.ac/problem/2733 题解 神仙题-- 首先可以观察到一个结论: 目标块的两块小三明治一定分别是最后和倒数第二个被吃的. 由此我们可以考虑这两块谁先被吃.这 ...

  7. LOJ #2734 Luogu P3615 [JOI2016春季合宿]Toilets (结论、贪心)

    题目链接 (loj) https://loj.ac/problem/2734 (luogu) https://www.luogu.org/problem/P3615 题解 嗯,考场上肝了\(3h\)然 ...

  8. [JOI2012春季合宿]Rotate (链表)

    题意 题解 又是一道神仙题-- 显然的做法是大力splay,时间复杂度\(O((N+Q)N\log N)\), 可以卡掉. 正解: 使用十字链表维护矩阵,在周围增加第\(0\)行/列和第\((n+1) ...

  9. [JOI2012春季合宿]Constellation (凸包)

    题意 题解 神仙结论题. 结论: 一个点集合法当且仅当其凸包上的两种颜色点分别连续. 证明: 必要性显然. 充分性: 考虑对于一个不同色三角形\(ABC\),不妨设点\(A\)为白点,点\(B,C\) ...

最新文章

  1. Python_面向对象_类1
  2. IDC:全球物联网支出将在2019年达到1.3万亿美元
  3. K-Means聚类算法Java实现
  4. ABAP很厉害是怎么一种体验?
  5. php面向对象编程详解,PHP面向对象编程
  6. MapReduce其他功能
  7. 云计算与分布式系统课程学习笔记(一)——云计算简介
  8. 中国双轴取向(BO)膜行业市场供需与战略研究报告
  9. mysql 分布式 生成序号_分布式唯一ID生成方案
  10. VS中的lib和dll的区别和使用
  11. Python-Matplotlib可视化(7)——多方面自定义统计图绘制
  12. Linux mint 14下的powerDNS+mysql+powerAdmin搭建个性DNS域名解析服务器
  13. 【项目篇- 封面后目录前的核心内容、优势展示部分如何打磨?(超全图文总结建议)】创新创业竞赛项目计划书、新苗国创(大创)申报书
  14. 华为鸿蒙研究多久了,鸿蒙华为研发了多少年_华为鸿蒙意义
  15. android 4.4 安装 flash,android 4.0 安装adobe flash player
  16. Html 排版与标签(一)
  17. Java七牛云报400错误
  18. 跳出舒适区发现更大的世界,来社科院杜兰金融管理硕士项目汲取能量挑战自我
  19. 【密码资料】纳瓦霍密码
  20. 机器学习(一)- feature scaling

热门文章

  1. Git新手入门视频教程
  2. linux中时间转换date
  3. 如何打印int整数的32位二进制数(位运算)
  4. 无敌破坏王2之大闹互联网观后感
  5. 奇安信渗透测试面试题库_奇安信面试经验
  6. 如何用十六进制换算十进制
  7. Jackson ImmunoResearch通过 SDS-PAGE 进行蛋白质分离
  8. Kd-Tree算法原理
  9. 宝塔防火墙可以防ddos攻击吗
  10. IP地址的认识(一)