UPC第42场题解

UPC第42场题解

A:奶酪

现有一块大奶酪,它的高度为 h ,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z = 0 ,奶酪的上表面为 z = h 。

现在,奶酪的下表面有一只小老鼠 Jerry ,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交, Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交, Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去 ?

P1(x1,y1,z1)、P2(x2,y2,z2)的距离公式如下:

img

1<=n<=1000,1<=h,r<=1000000000,T<=20 坐标绝对值不超过1000000000

输入

包含多组数据。
第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。
接下来是 T 组数据,每组数据的格式如下:
第一行包含三个正整数 n,h 和 r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的 n 行,每行包含三个整数 x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为(x,y,z)

输出

输出包含T行,分别对应T组数据的答案,如果在第i组数据中,Jerry能从下表面跑到上表面,则输出Yes,如果不能,则输出No(均不包含引号)。

样例输入

3
2 4 1
0 0 1
0 0 3
2 5 1
0 0 1
0 0 4
2 5 2
0 0 2
2 0 4

样例输出

Yes
No
Yes

n<=1000,考虑直接打暴力dfs搜索所用情况,由于起点不确定,所以枚举起点,从这点小鼠进入并递归判断是否存在通路通往上表面。

c o d e : code: code:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include <vector>
#include <set>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1005,mod = 998244353;
struct node
{
   ll x,y,z;
}jk[maxn];
int t,n;
ll r,h;
bool vis[maxn];
bool check(int k1,int k2)  //判断是否可以连通
{
    ll sum=(jk[k1].x-jk[k2].x)*(jk[k1].x-jk[k2].x)+(jk[k1].y-jk[k2].y)*(jk[k1].y-jk[k2].y)+(jk[k1].z-jk[k2].z)*(jk[k1].z-jk[k2].z);
    return sum<=r*r*4;
}
bool dfs(int m)
{
    vis[m]=1;
    if(jk[m].z+r>=h) //如果可以到达上表面
    {
        return 1;
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        if(check(m,i))
        {
            if(dfs(i))
            {
                return 1;
            }
        }
    }
    return 0;
}
int main()
{

   cin >> t;
   while(t--)
   {
       memset(vis,0,sizeof(vis));
       scanf("%lld %lld %lld",&n,&h,&r);
       for(int i=1;i<=n;i++)
       {
           scanf("%lld %lld %lld",&jk[i].x,&jk[i].y,&jk[i].z);
       }
       int fl=0;
       for(int i=1;i<=n;i++)
       {
           if(jk[i].z<=r) //判断小鼠是否可以进入
           {
                if(dfs(i))
           {
               fl=1;
               break;
           }
           }

       }
       if(fl) printf("Yes\n");
       else printf("No\n");
   }
}

C: Haybale Restacking

Farmer John has just ordered a large number of bales of hay. He would like to organize these into N piles (1 <= N <= 100,000) arranged in a circle, where pile i contains B_i bales of hay. Unfortunately, the truck driver delivering the hay was not listening carefully when Farmer John provided this information, and only remembered to leave the hay in N piles arranged in a circle. After delivery, Farmer John notes that pile i contains A_i bales of hay. Of course, the A_i’s and the B_i’s have the same sum.

Farmer John would like to move the bales of hay from their current configuration (described by the A_i’s) into his desired target configuration (described by the B_i’s). It takes him x units of work to move one hay bale from one pile to a pile that is x steps away around the circle. Please help him compute the minimum amount of work he will need to spend.

给出n块土地,现有泥土A[i],需要改造成B[i],但这次土地排列成环,且不可买进买出,只能运,且 ∑ a [ i ] = ∑ b [ i ] \sum a[i]=\sum b[i] a[i]=b[i],问最小花费。

输入

* Line 1: The single integer N.
* Lines 2…1+N: Line i+1 contains the two integers A_i and B_i (1 <= A_i, B_i <= 1000).

输出

* Line 1:the minimum amount of work he will need to spend.

样例输入

4
7 1
3 4
9 2
1 13

样例输出

13

把环砍掉,不就是均分纸牌吗。

我们假设1->n k;

2->1 b[1]-(a[1]-k)=b[1]-a[1]+k;

3->2 b[1]+b[2]-a[1]-a[2]+k;

.4->3 b[1]+b[2]+b[3]-a[1]-a[2]-a[3]+k;

n->n-1 ∑ i = 1 n − 1 ( b [ i ] − a [ i ] ) + k \sum_{i=1}^{n-1}(b[i]-a[i])+k i=1n1(b[i]a[i])+k

c [ i ] = c [ i − 1 ] + b [ i ] − a [ i ] c[i]=c[i-1]+b[i]-a[i] c[i]=c[i1]+b[i]a[i],最后的答案也就是 ∑ ∣ c [ i ] + k ∣ \sum|c[i]+k| c[i]+k,推到这里,跟前两天有个题一样,当k取c数组的中位数取反显然最优。

c o d e : code: code:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include <vector>
#include <set>
#include<algorithm>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100005,mod = 998244353;
int b[maxn],a[maxn],d[maxn];
int main()
{
   int n;
   cin >> n;
   int m=(n+1)/2;
   for(int i=1;i<=n;i++)
   {
       cin >> a[i] >> b[i];
       a[i]-=b[i];
   }
   for(int i=2;i<=n;i++)
   {
       d[i]=d[i-1]+a[i-1];
   }
   d[1]=d[n]+a[n];
   sort(d+1,d+n+1);
   ll ans=0;
   for(int i=1;i<=n;i++)
   {
       ans+=abs(d[i]-d[m]);
   }
   cout << ans;
}

H: Connect the Cows

Every day, Farmer John walks around his farm to check on the health and well-being of his N (1 <= N <= 10) cows.

The location of each cow is described by a point in the 2D plane, and Farmer John starts out at the origin (0,0). To make his route more interesting, Farmer John decides that he will only walk in directions parallel to the coordinate axes – that is, only north, south, east, or west. Furthermore, he only changes his direction of travel when he reaches the location of a cow (he may also opt to pass through the location of a cow without changing direction, if desired). When he changes his direction of travel, he may make either a 90-degree or 180-degree turn. FJ’s route must take him back to the origin after visiting all his cows.

Please compute the number of different routes FJ can take to visit his N cows, if he changes direction exactly once at the location of each cow. He is allowed to pass through the location of a cow without changing direction an arbitrary number of times. The same geometric route taken forward versus backward counts as two different routes.

在一个农场上,视为一个坐标系,而农夫有n头牛(n<=10),每头牛有相应的坐标,他需要从(0,0)的位置开始,往上下左右走,遍历每一头牛且只能一次,而且他一定要撞到一头牛之后才可以选择转弯,最后必须要回到原点的。在这里有一个坑是题目没有说清楚的,就是必须是经过牛时选择转弯了才可以算是经过这头牛

输入

* Line 1: The integer N.
* Lines 2…1+N: Line i+1 contains the x and y coordinates (space-separated) of the ith point (each values is in the range -1000…1000).

输出

* Line 1: The number of different routes FJ can take (this could be zero if there are no valid routes).

样例输入

4
0 1
2 1
2 0
2 -5

样例输出

2

注意到n<=10,我们可以直接使用全排列模拟出所有路线顺序,dfs判断是否可行即可。

c o d e : code: code:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include <vector>
#include <set>
#include<algorithm>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1005,mod = 998244353;
int p[14],fl,n;
int x[15],y[15];
int judge(int x1,int y1,int x2,int y2) 
{
    if (x1!=x2&&y1!=y2)return -1;
    if (x1==x2&&y1==y2)return -1;
    if (x1==x2&&y1<y2) return 0;
    if (x1==x2&&y1>y2) return 1;
    if (y1==y2&&x1<x2) return 2;
    if (y1==y2&&x1>x2) return 3;
}
void dfs(int k)
{
    if(fl==0||k>n)
    {
        return ;
    }
     if(judge(x[p[k]],y[p[k]],x[p[k+1]],y[p[k+1]])==-1) 
     {
        fl=0;
        return;
    }
    if( judge(x[p[k+1]],y[p[k+1]],x[p[k]],y[p[k]] ) == judge( x[p[k]],y[p[k]],x[p[k-1]],y[p[k-1]] ))
    {
        fl=0;
        return;
   }
    dfs(k+1);
}

int main()
{

    cin >> n;
    for(int i=1;i<=n;i++)
    {
        cin >> x[i] >> y[i];
        p[i]=i;
    }
    int ans=0;
    do{
        fl=1;
        dfs(0);
        if(fl==1) ans++;
    }while(next_permutation(p+1,p+n+1));
    cout << ans ;
}

F: Range Flip Find Route

Consider a grid with H rows and W columns of squares. Let (r,c) denote the square at the r-th row from the top and the c-th column from the left. Each square is painted black or white.
The grid is said to be good if and only if the following condition is satisfied:
From (1,1), we can reach (H,W) by moving one square right or down repeatedly, while always being on a white square.
Note that (1,1) and (H,W) must be white if the grid is good.
Your task is to make the grid good by repeating the operation below. Find the minimum number of operations needed to complete the task. It can be proved that you can always complete the task in a finite number of operations.
Choose four integers
r0,c0,r1,c1(1≤r0≤r1≤H,1≤c0≤c1≤W). For each pair r,c (r0≤r≤r1,c0≤c≤c1), invert the color of (r,c) - that is, from white to black and vice versa.

给出只包含.#的 H*W网格,每次操作指定$ r_0,\ c_0,\ r_1,\ c_1\ (1 \le r_0 \le r_1 \le H,\ 1 \le c_0 \le c_1 \le L)$,使的.##.

操作结束后,有一条从(1, 1) 到 (H, W) 的路径,满足:

  • 只向右或向下移动。
  • 只经过.

求最小操作数。

Constraints
·2≤H,W≤100

输入

Input is given from Standard Input in the following format:
img
Here src represents the color of (r,c) - # stands for black, and . stands for white.

输出

Print the minimum number of operations needed.

样例输入

【样例1】
3 3
.##
.#.
##.
【样例2】
2 2
#.
.#
【样例3】
4 4
..##
#...
###.
###.
【样例4】
5 5
.#.#.
#.#.#
.#.#.
#.#.#
.#.#.

样例输出

【样例1】
1
【样例2】
2
【样例3】
0
【样例4】
4

这个题有点像普通棋盘问题的变形,但无疑增大了难度,考虑DP求解,首先这个题只能向下走,或者像右走,我们设 d p [ i ] [ j ] dp[i][j] dp[i][j] 为(1,1)到(i,j)的最小操作数;

从上向下看,如果 d p [ i − 1 ] [ j ] = dp[i-1][j]= dp[i1][j]=".", d p [ i ] [ j ] = dp[i][j]= dp[i][j]="#",我们需要翻一次,否则, d p [ i ] [ j ] = d [ i − 1 ] [ j ] dp[i][j]=d[i-1][j] dp[i][j]=d[i1][j],这是因为如果两个本身都是通的,那我们直接就不用管,如果前一不通那我们可以翻前一个的时候把后一个翻了。从左向右同理。

所以我们不难得出下面的方程:

           if(a[i-1][j]=='.'&&a[i][j]=='#')
            {
                k1=1;
            }
            if(a[i][j-1]=='.'&&a[i][j]=='#')
            {
                k2=1;
            }
            dp[i][j]=min(dp[i-1][j]+k1,dp[i][j-1]+k2);

最后我们要注意初始化,对于(1,1)点要特殊处理,第一行和第一列赋值即可。

c o d e : code: code:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include <vector>
#include <set>
#include<algorithm>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=105,mod = 998244353;
char a[maxn][maxn];
int dp[maxn][maxn];
int main()
{
    int n,m;
    cin >> n >> m;
    for(int i=1;i<=n;i++)
    {
      for(int j=1;j<=m;j++)
      {
          cin >> a[i][j];
      }
    }
    if(a[1][1]=='#')
    {
        dp[1][1]=1;
    }
    for(int i=2;i<=m;i++)
    {
        if(a[1][i-1]=='.'&&a[1][i]=='#')
        {
            dp[1][i]=dp[1][i-1]+1;
        }
        else
        {
            dp[1][i]=dp[1][i-1];
        }
    }
    for(int i=2;i<=n;i++)
    {
        if(a[i-1][1]=='.'&&a[i][1]=='#')
        {
            dp[i][1]=dp[i-1][1]+1;
        }
        else
        {
            dp[i][1]=dp[i-1][1];
        }
    }

    for(int i=2;i<=n;i++)
    {
        for(int j=2;j<=m;j++)
        {
            int k1=0,k2=0;
            if(a[i-1][j]=='.'&&a[i][j]=='#')
            {
                k1=1;
            }
            if(a[i][j-1]=='.'&&a[i][j]=='#')
            {
                k2=1;
            }
            dp[i][j]=min(dp[i-1][j]+k1,dp[i][j-1]+k2);
        }
    }
    cout << dp[n][m];
}

J: Flowerpot

老板需要你帮忙浇花。给出N滴水的坐标,y表示水滴的高度,x表示它下落到x轴的位置。

img

每滴水以每秒1个单位长度的速度下落。你需要把花盆放在x轴上的某个位置,使得从被花盆接着的第1滴水开始,到被花盆接着的最后1滴水结束,之间的时间差至少为D。

我们认为,只要水滴落到x轴上,与花盆的边沿对齐,就认为被接住。给出N滴水的坐标和D的大小,请算出最小的花盆的宽度W。输入

第一行2个整数 N 和 D。
第2… N+1行每行2个整数,表示水滴的坐标(x,y)。
1 ≤ N ≤ 100000,1 ≤ D ≤ 1000000,0≤x,y≤106。

输出

仅一行1个整数,表示最小的花盆的宽度。如果无法构造出足够宽的花盆,使得在D单位的时间接住满足要求的水滴,则输出-1。

样例输入

4 5
6 3
2 4
4 10
12 15

样例输出

2

问题可以简化为:给出一条线段上的 n 个点,每个点有一个权值 y i y_i yi ,你要找出一段区间,使区间中的 y m a x − y m i n > d y_{max}-y_{min}>d ymaxymin>d。输出这个区间的长度.

要想确定区间的长度,显然可以二分,但是这里我们可以采用滑动窗口来解决区间问题,并通过两个单调队列去记录区间最大值和最小值。

最外层循环拉左端点(因为要里面右端点移完再动左端点)每次先用两个while循环更新队首。然后第二层循环移动右端点,直到达到右边界或满足条件(最大值-最小值 > d)。里面再用两个循环类似滑动窗口更新最大值队列和最小值队列(只要队中还有元素且新加入的元素比队尾大(维护最大值时为小)那么就队尾出队。第二层循环结束后,如果满足条件就更新 ans。

c o d e : code: code:

#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
#include <vector>
#include <set>
#include<algorithm>
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100005,mod = 998244353;
struct node
{
    int x,y;
}jk[maxn];
int ans,d,n;
bool cmp(node x,node y)
{
    return x.x<y.x;
}
int p1[maxn],p2[maxn]; // 存最大值,最小值的位置
int h1,h2,t1,t2;  //两个队列的头尾指针。
int main()
{
   cin >> n >> d;
   for(int i=1;i<=n;i++)
   {
       cin >> jk[i].x >> jk[i].y;
   }
   sort(jk+1,jk+n+1,cmp);
   ans=0x3f3f3f3f;
   h1=1,h2=1;
   for(int l=0,r=0;l<=n;l++)//左端点移动,
   {
       while(h1<=t1&&p1[h1]<l) h1++;//要保证队首不能在左端点以前。
       while(h1<=t2&&p2[h2]<l) h2++;
       while(jk[p1[h1]].y-jk[p2[h2]].y<d&&r<n)
       {
           r++;//右端点移动。
           while(h1<=t1&&jk[p1[t1]].y<jk[r].y) t1--; 
           p1[++t1]=r;
           while(h2<=t2&&jk[p2[t2]].y>jk[r].y) t2--; 
           p2[++t2]=r;
       }
       if(r!=n) ans=min(ans,jk[r].x-jk[l].x); 
   }
   if(ans>=0x3f3f3f3f)
   {
       cout << -1;
   }
   else
   {
       cout << ans;
   }
}

1,cmp);
ans=0x3f3f3f3f;
h1=1,h2=1;
for(int l=0,r=0;l<=n;l++)//左端点移动,
{
while(h1<=t1&&p1[h1]<l) h1++;//要保证队首不能在左端点以前。
while(h1<=t2&&p2[h2]<l) h2++;
while(jk[p1[h1]].y-jk[p2[h2]].y<d&&r<n)
{
r++;//右端点移动。
while(h1<=t1&&jk[p1[t1]].y<jk[r].y) t1–;
p1[++t1]=r;
while(h2<=t2&&jk[p2[t2]].y>jk[r].y) t2–;
p2[++t2]=r;
}
if(r!=n) ans=min(ans,jk[r].x-jk[l].x);
}
if(ans>=0x3f3f3f3f)
{
cout << -1;
}
else
{
cout << ans;
}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

\ 安 /

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值