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代码如下: