zoukankan      html  css  js  c++  java
  • USACO银组12月月赛题解

    # USACO银组12月月赛题解 ## Convention ### 题面 一场别开生面的牛吃草大会就要在Farmer John的农场举办了! 世界各地的奶牛将会到达当地的机场,前来参会并且吃草。具体地说,有N头奶牛到达了机场(1≤N≤105),其中奶牛i在时间ti(0≤ti≤109)到达。Farmer John安排了M(1≤M≤105)辆大巴来机场接这些奶牛。每辆大巴可以乘坐C头奶牛(1≤C≤N)。Farmer John正在机场等待奶牛们到来,并且准备安排到达的奶牛们乘坐大巴。当最后一头乘坐某辆大巴的奶牛到达的时候,这辆大巴就可以发车了。Farmer John想要做一个优秀的主办者,所以并不想让奶牛们在机场等待过长的时间。如果Farmer John合理地协调这些大巴,等待时间最长的奶牛等待的时间的最小值是多少?一头奶牛的等待时间等于她的到达时间与她乘坐的大巴的发车时间之差。 输入保证MC≥N。 #### 输入格式 (文件名:convention.in): 输入的第一行包含三个空格分隔的整数N,M,和C。第二行包含N 个空格分隔的整数,表示每头奶牛到达的时间。 #### 输出格式 (文件名:convention.out): 输出一行,包含所有到达的奶牛中的最大等待时间的最小值。 #### 输入样例: 6 3 2 1 1 10 14 4 3 #### 输出样例: 4 如果两头时间1到达的奶牛乘坐一辆巴士,时间2和时间4到达的奶牛乘坐乘坐第二辆,时间10和时间14到达的奶牛乘坐第三辆,那么等待时间最长的奶牛等待了4个单位时间(时间10到达的奶牛从时间10等到了时间14)。 ### 题解 - 看到题面以后以为是摆渡车... - 不过根据输出格式所说的最大的最小很容易想到二分答案。 题目中求得是等待的时间,因此我们就对等待的时间进行二分答案即可。 - 如何写二分答案的判断函数呢?? 线性扫描区间: 如果当前奶牛之前没有坐车,就坐一辆车,等接下来的车 如果当前奶牛的时间与第一头上车的奶牛时间差在mid以内,且容量够,上车 否则另开一辆车 最后得到所需车的数量 - 判定:车的数量小于m则合法 - 时间复杂度$O(nlogn)$
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100000;
    int n,m,c;
    int t[maxn]={};
    inline bool check(int x)
    {
    	int sum=0,cnt=0,last=0;
    	for (int i=1;i<=n;++i)
    	{
    		if (t[i]-last<=x && i!=1 && sum<c) 
    			sum++;//如果能乘车
    		else
    			last=t[i],sum=1,cnt++;//换新车
    	}
    	return cnt<=m;
    }
    int main()
    {
    	freopen("convention.in","r",stdin);
    	freopen("convention.out","w",stdout);
    	scanf("%d%d%d",&n,&m,&c);
    	for (int i=1;i<=n;++i) scanf("%d",t+i);
    	sort(t+1,t+n+1);//排序
    	int l=0,r=1e10;
    	while (l+1<r)
    	{
    		int mid=l+r>>1;
    		if (check(mid)) r=mid;
    		else l=mid;
    	}//二分答案
    	if (check(l)) printf("%d",l);
    	else printf("%d",r);
    	return 0;
    } 
    

    convention2

    题面

    虽然在接机上耽误了挺长时间,Farmer John为吃草爱好牛们举行的大会至今为止都非常顺利。大会吸引了世界各地的奶牛。
    然而大会的重头戏看起来却给Farmer John带来了一些新的安排上的困扰。他的农场上的一块非常小的牧草地出产一种据某些识货的奶牛说是世界上最美味的品种的草。因此,所有参会的N头奶牛(1≤N≤105)都想要品尝一下这种草。由于这块牧草地小到仅能容纳一头奶牛,这很有可能会导致排起长龙。
    Farmer John知道每头奶牛i计划到达这块特殊的牧草地的时间ai,以及当轮到她时,她计划品尝这种草花费的时间ti。当奶牛i开始吃草时,她会在离开前花费全部ti的时间,此时其他到达的奶牛需要排队等候。如果这块牧草地空出来的时候多头奶牛同时在等候,那么资历最深的奶牛将会是下一头品尝鲜草的奶牛。在这里,恰好在另一头奶牛吃完草离开时到达的奶牛被认为是“在等待的”。类似地,如果当没有奶牛在吃草的时候有多头奶牛同时到达,那么资历最深的奶牛是下一头吃草的奶牛。
    请帮助FJ计算所有奶牛中在队伍里等待的时间(ai到这头奶牛开始吃草之间的时间)的最大值。

    输入格式

    文件名:convention2.in:
    输入的第一行包含N。以下N行按资历顺序给出了N头奶牛的信息(资历最深的奶牛排在最前面)。每行包含一头奶牛的ai和ti。所有的ti为不超过104的正整数,所有ai为不超过109的正整数。

    输出样例

    文件名:convention2.out:
    输出所有奶牛中的最长等待时间。

    输入样例:

    5
    25 3
    105 30
    20 50
    10 17
    100 10

    输出样例:

    10
    在这个例子中,我们有5头奶牛(按输入中的顺序编号为1..5)。奶牛4最先到达(时间10),在她吃完之前(时间27)奶牛1和奶牛3都到达了。由于奶牛1拥有较深的资历,所以她先吃,从她到达开始共计等待了2个单位时间。她在时间30结束吃草,随后奶牛3开始吃草,从她到达开始共计等待了10单位时间。在一段没有奶牛吃草的时间过后,奶牛5到达,在她正在吃草的时间里奶牛2也到达了,在5个单位时间之后能够吃到草。相比到达时间等待最久的奶牛是奶牛3。

    题解

    • 到了初中以后再次受到了牛吃草问题的洗礼
    • 想到暴力模拟应该很简单,但是时间复杂度不优秀。我们选择数据结构维护。
    • 我们发现要求某些符合条件的最优值:即每一头牛[在它吃完草结束前到达]的[资历最深]的奶牛,即符合的条件是吃完草前到达,最优值是资历最深。最优值?这不就是优先队列嘛。因此我们可以用优先队列来维护。
    • 具体做法是:
      暴力找出第一个吃草的牛:按照到达时间为第一关键字,按照资历为第二关键字的顺序的第一个元素。然后将其放入堆中。
      如果堆是空的:新放进去。
      如果不为空:顺序查找来到时间小于这个牛吃草结束的牛,进堆;以经验为第一关键字。每一次的堆顶就是即将吃的草。
    • 预处理:预处理经验值,第i头可以认为是(n-i+1)
    • 时间复杂度:不高于(O(nlogn))
    • 代码实现如下:
    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+10;
    int n,ans=0;
    struct node {
    	int tim,exp,eat,lt;
    }a[maxn];
    inline bool cmp(node a,node b) {
    	if (a.tim!=b.tim) return a.tim<b.tim;
    	else return a.exp>b.exp;
    }//排序
    int main(void)
    {
    	freopen("convention2.in","r",stdin);
    	freopen("convention2.out","w",stdout);
    	scanf("%d",&n);
    	for (int i=1;i<=n;++i) 
    	{
    	    scanf("%d",&a[i].tim);
    	    scanf("%d",&a[i].eat);
    	    a[i].exp=n-i+1;
    	}
    	sort(a+1,a+n+1,cmp);
    	priority_queue < pair< int , int > > q;
    	q.push(make_pair(a[1].exp,1));//用pair类型存储,经验为第一关键字
    	int now=1,last_time=a[1].tim,cnt=0;
    	while (now<=n)//now表示进堆元素
    	{
    		while (q.size())//堆为空
    		{
    			int p=q.top().second;q.pop();
    			ans=max(ans,last_time-a[p].tim);//更新等待时间
    			while (a[now+1].tim<=last_time+a[p].eat && now<n)
    			{
    				now++;
    				q.push(make_pair(a[now].exp,now));
    			}//将结束时间前的进堆
    			last_time+=a[p].eat;//last_time存储上一头牛结束吃草的时间
    		}
    		now++,last_time=a[now].tim;
    		q.push(make_pair(a[now].exp,now));//新进堆.如果now到n了也不要紧,下一层反正会退出
    	}
    	cout<<ans<<endl;
    	return 0;
    } 
    
    

    Mooyo Mooyo

    题面

    由于手上(更确实的,蹄子上)有大把的空余时间,Farmer John的农场里的奶牛经常玩电子游戏消磨时光。她们最爱的游戏之一是基于一款流行的电子游戏Puyo Puyo的奶牛版;名称当然叫做Mooyo Mooyo。
    Mooyo Mooyo是在一块又高又窄的棋盘上进行的游戏,高(N)(1 leq N leq 100))格,宽10格。 这是一个(N = 6)的棋盘的例子:
    0000000000
    0000000300
    0054000300
    1054502230
    2211122220
    1111111223
    每个格子或者是空的(用0表示),或者是九种颜色之一的干草捆(用字符1..9表示)。重力会使得干草捆下落,所以没有干草捆的下方是0。
    如果两个格子水平或垂直方向直接相邻,并且为同一种非0颜色,那么这两个格子就属于同一个连通区域。任意时刻出现至少(K)个格子构成的连通区域,其中的干草捆就会全部消失,变为0。如果同时出现多个这样的连通区域,它们同时消失。随后,重力可能会导致干草捆向下落入某个变为0的格子。由此形成的新的布局中,又可能出现至少(K)个格子构成的连通区域。若如此,它们同样也会消失(如果又有多个这样的区域,则同时消失),然后重力又会使得剩下的方块下落,这一过程持续进行,直到不存在大小至少为(K)的连通区域为止。
    给定一块Mooyo Mooyo棋盘的状态,输出这些过程发生之后最终的棋盘的图案。

    输入格式

    文件名:mooyomooyo.in:
    输入的第一行包含(N)(K)(1 leq K leq 10N))。以下(N)行给出了棋盘的初始状态。

    输出格式

    文件名:mooyomooyo.out:
    输出(N)行,描述最终的棋盘状态。

    输入样例:

    6 3
    0000000000
    0000000300
    0054000300
    1054502230
    2211122220
    1111111223

    输出样例:

    0000000000
    0000000000
    0000000000
    0000000000
    1054000000
    2254500000

    在上面的例子中,如果K=3
    ,那么存在一个大小至少为K

    的颜色1的连通区域,同样有一个颜色2的连通区域。当它们同时被移除之后,棋盘暂时成为了这样:

    0000000000
    0000000300
    0054000300
    1054500030
    2200000000
    0000000003

    然后,由于重力效果,干草捆下落形成这样的布局:

    0000000000
    0000000000
    0000000000
    0000000000
    1054000300
    2254500333

    再一次地,出现了一个大小至少为K

    地连通区域(颜色3)。移除这个区域就得到了最终的棋盘布局:

    0000000000
    0000000000
    0000000000
    0000000000
    1054000000
    2254500000

    题解

    • 大模拟...码农题...头很大啊...
      我们暂且叫它俄罗斯方块消消乐
    • 这道题分为3部:判断存在能消的方块→消掉方块→下落
      每一个都必须与dfs(flood-fiil)来模拟,因为方格的形状是不固定的。
    • 第一步:暴力求联通块,顺便求出和每一个联通块所在的联通块大小。有≥k的就继续,否则就结束循环。搞个死循环就好,让电脑慢慢搞
      第二部:消方块。根据第一步求出的数字,≥k的搞成0即可。
      第三部:下落。按照行数倒序枚举。最下那么先枚举到的一定是最底层的,叠在最小面并且从下往上叠。至于怎么叠?用一个数组标记即可。
    • 对于代码的解释:搜索的代码占了大部分,本质相同,因为具体操作不同所以分开来谢了。用函数写应该还是比较直观的了吧!
    • 代码如下:
    #include<bits/stdc++.h>
    using namespace std;
    int n,k;
    int vd[1000];
    int f[101][11];
    int v[101][11];
    int u[101][11];
    int nm[101][11];
    char a[101][11];
    char temp[101][11];
    int dx[4]={1,-1,0,0};
    int dy[4]={0,0,-1,1};
    int num(int x,int y)
    {
    	int sum=1;
    	v[x][y]=1;
    	for (int i=0;i<4;++i)
    	{
    		int nx=x+dx[i],ny=y+dy[i];
    		if (nx>=1 && nx<=n && ny>=1 && ny<=10)
    		if (!v[nx][ny] && a[x][y]==a[nx][ny]) sum+=num(nx,ny);
    	}
    	return sum;
    }
    //求x,y所在连通块大消息 
    void cover(int x,int y)
    {
        u[x][y]=1;
        for (int i=0;i<4;++i)
        {
        	int nx=x+dx[i],ny=y+dy[i];
        	if (nx>=1 && nx<=n && ny>=1 && ny<=10)
        	if (!u[nx][ny] && a[x][y]==a[nx][ny])
        	{
        		nm[nx][ny]=nm[x][y];
        		cover(nx,ny);
    		}
    	}
    	return;
    }
    //求出某一个节点所在连通块大小后不断覆盖,节约时间 
    bool many(void)
    {
    	memset(v,0,sizeof(v));
    	memset(u,0,sizeof(u));
    	for (int i=1;i<=n;++i)
    	    for (int j=1;j<=10;++j)
    	        if (nm[i][j]==0) 
    	        {
    	        	nm[i][j]=num(i,j);
    	        	cover(i,j);
    			}
    	for (int i=1;i<=n;++i)
    	    for (int j=1;j<=10;++j)
    	        if (nm[i][j]>=k && a[i][j]!='0') return true;
    	return false;
    }
    //判断要不要继续消下去 
    void die(int x,int y)
    {
    	v[x][y]=1;
    	char now=a[x][y];
    	a[x][y]='0';
        for (int i=0;i<4;++i)
        {
        	int nx=x+dx[i],ny=y+dy[i];
        	if (nx>=1 && nx<=n && ny>=1 && ny<=10)
        	if (!v[nx][ny] && now==a[nx][ny]) die(nx,ny);
    	}
    }
    //消成0 
    void noit(void)
    {
    	for (int i=1;i<=n;++i)
    	    for (int j=1;j<=10;++j)
    		    if (a[i][j]!='0' && nm[i][j]>=k) 
    		        die(i,j);
    	return;
    }
    //枚举消的坐标 
    void downit(void)
    {
    	memset(temp,'0',sizeof(temp));
    	for (int i=1;i<=100;++i) vd[i]=n+1;
    	//掉落位置的预处理 
    	for (int i=n;i;--i)
    	    for (int j=1;j<11;++j)
    		    if (a[i][j]>'0') vd[j]--,temp[vd[j]][j]=a[i][j];
    	//temp用来暂时存储图像 
    	for (int i=1;i<=n;++i)
    	    for (int j=1;j<11;++j)
    	        a[i][j]=temp[i][j];
    	return;
    } 
    //掉落 
    void print(void)
    {
    	for (int i=1;i<=n;++i)
    	    for (int j=1;j<=10;++j)
    	        if (j==10) 
    	        {
    	        	if (i==n) cout<<a[i][j];
    	        	else cout<<a[i][j]<<'
    ';
    			}
    	        else cout<<a[i][j];
        cout<<endl;
    }
    //打印结果 
    int main(void)
    {
    	freopen("mooyomooyo.in","r",stdin);
    	freopen("mooyomooyo.out","w",stdout);
    	cin>>n>>k;
    	for (int i=1;i<=n;++i) 
    	    for (int j=1;j<=10;++j)
    	        cin>>a[i][j];
    	while (many()==true) 
    	{
    	    memset(v,0,sizeof(v));
    	    memset(u,0,sizeof(u));
    		noit();
    		downit();
    		memset(nm,0,sizeof(nm));
    	}
    	print();
    	return 0;
    }
    
  • 相关阅读:
    gym-102307 D. Do Not Try This Problem
    AtCoder Beginner Contest 161 E
    Codeforces 1270E 构造+数学
    2019牛客暑期多校训练营(第七场)E 线段树+离散化区间
    codeforces 1272F dp+记录路径
    Focus相关点滴
    Command模式
    接口隔离原则(ISP)
    依赖倒置原则(DIP)
    Liskov替换原则(LSP)
  • 原文地址:https://www.cnblogs.com/pigzhouyb/p/10124342.html
Copyright © 2011-2022 走看看