您的位置:首页 > 其它

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

2017-12-28 20:47 169 查看
        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
,q
,A
,B
,C
,ord
;
bool bo

; ll ans=1ll<<60;
struct node{ ll x; int y; }a
,b
,c
;
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
;
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
.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
,a
,b
,fst
,pnt[N<<3],len[N<<3],nxt[N<<3];
int lf
,rg
,vis
;
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
,a0
,lg2
,lf
,rg
; ll a
;
struct node{ ll x; int y; }f[17]
,g[17]
;
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
-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
,nxt
,a
,b
,c
,rst
; ll tg
,icr
;
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
,b
,s
;
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
,q
,f
[17],g
[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
[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
,p
,b
,f
,g
,s
;
struct node{ ll x,y; }a
,q
;
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
,pnt
,nxt
,a
,last
,lg2
,f[19]
,g[19]
,lf
,rg
;
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
,pnt[N<<1],nxt[N<<1],p
,h
,d
,vis
; bool bo
;
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);
} 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<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
,b
,f[4][16]
;
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
,pnt[N<<1],nxt[N<<1],a
,lf
,rg
;
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
,last
,nxt
,ans1
,ans2
;
struct point{ int x,y; }w
,A,B;
struct node{ point p; int pos,id; }p
,q
;
struct trp{ int x,y,k; bool bo; }a
,a0
;
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
,lf
,rg
,a
,b
;
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
,py
,len
,nxt
;
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: