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

这篇博客详细介绍了作者在2017年JOI春季合宿期间遇到的算法题目,包括Day1的T1 Cultivation、T2 Port Facility、T3 Sparklers,Day2的T1 Arranging Tickets和T2 Broken Device,以及Day3的T1 Long Distance Coach和T2 Long Mansion等。通过二分、贪心、动态规划等方法解决各种问题,如仙人掌种植优化、货物装货方式计算、烟花传递最短时间、乘客路线规划、通信编码传输和长途驾驶补给策略等。博客内容涉及多种算法思想和复杂度分析,并给出了AC代码。
摘要由CSDN通过智能技术生成

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


                
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值