杭电多校(MINIEYE)第三场 补题

1003 Forgiving Matching

题目

问题描述

Alice和Bob正在玩游戏。在这个游戏中,二维平面上有 n 条直线。 Alice 将首先在所有 n 条直线中选择恰好 k 条直线 l1,l2,…,lk,然后 Bob 将画一条直线 L。 Bob 的惩罚定义为 {l1,l2,…, lk} 与 L 共享至少一个公共点。请注意,两条重叠的线也共享公共点。
Alice 想要最大化 Bob 的惩罚,而 Bob 想要最小化它。你将得到这 n 行,请编写一个程序来预测 Bob 对 k=1,2,3,…,n 的惩罚,如果两个玩家都玩得最好。

输入
第一行包含一个整数 T (1≤T≤500),即测试用例的数量。对于每个测试用例:

第一行包含一个整数 n (1≤n≤100000),表示直线的数量。

接下来的 n 行中的每一行都包含四个整数 xai,yai,xbi 和 ybi (0≤xai,yai,xbi,ybi≤109),表示一条直线通过 (xai,yai) 和 (xbi,ybi)。 (xai,yai) 永远不会与 (xbi,ybi) 重合。

保证所有 n 的总和最多为 1000000。

输出
对于每个测试用例,输出n行,其中第i行(1≤i≤n)包含一个整数,表示当k=i时Bob的惩罚。

样本输入

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

样本输出

0
1
0
0
0

解题思路

两条直线不存在公共点当且仅当它们平行即斜率相同。

已知现在有k条直线,Bob为了使有交点的直线尽可能少,就要使与直线L平行的直线尽可能多,因此其最小惩罚就是k-n_max

Alice 为了让Bob 与尽量多的直线相交,就是要使斜率出现次数的最大值最小,即最大化惩罚就要最小化n_min

因此,首先计算出所有直线的斜率,然后从小到大排序,然后不断从前向后循环取斜率,每轮每种斜率只取一个,这样能够保证最小化数量最多的斜率的数量。

代码

#include<bits/stdc++.h>
const int maxn=1e5+5;
int t,n;
map<pair<int,int>,int>M;
int gcd(int x,int y)
{
    return y?gcd(y,x%y):x
}
int abs(int s)
{
    return x<0?-x:x;
}
struct node
{
    int no,num;
    bool operator<(const node &other) const { return num > other.num}
}num[maxn];
int main()
{
    for(scanf("%d",&t);t--;)
    {
        int top=0;
        M.clear();
        scanf("%d",&n);
        for(int i=1,xi,x2,y1,y2;i<=n;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            int y=y2-y1,x=x2-x1;
            int f=1ll*y*x<0?-1:1;
            int g=gcd(abs(x),abs(y));
            y=abs(y)/g,x=abs(x)/g;
            if(!M.count({f*y,x})) M[{f*y,x}]=++top,num[top]={top,0};
            num[M[{f*y,x}]].num++;
        }
        sort(num+1,num+top+1);
        int now=1;
    }
}

1009 Rise in Price

题目

问题描述
网格上有 n×n 个单元格,左上角的单元格在 (1,1) 处,而右下角的单元格在 (n,n) 处。你从 (1,1) 开始,然后移动到 (n,n)。在任何单元格 (i,j) 处,您可以移动到 (i+1,j) 或 (i,j+1),前提是您不移出网格。显然,您将精确地进行 2n−2 步。

当你在格子(i,j),包括起点(1,1)和目的地(n,n),你可以拿走这个格子里所有的ai,j钻石,有机会提价每颗钻石按 bi,j 美元。您将最终以最终价格出售您拥有的所有钻石,您的目标是选择将使您的利润最大化的最佳路径。请注意,最初每颗钻石的价格为零,您没有什么可出售的。

输入
第一行包含一个整数 T (1≤T≤10),即测试用例的数量。对于每个测试用例:

第一行包含一个整数 n (1≤n≤100),表示网格的大小。

以下n行每行包含n个整数,第i行包含ai,1,ai,2,…,ai,n (1≤ai,j≤106),表示每个单元格中的菱形数量。

以下n行每行包含n个整数,第i行包含bi,1,bi,2,…,bi,n (1≤bi,j≤106),表示每个单元格可以提价多少.

保证 ai,j 和 bi,j 的所有值都是从 [1,106] 中的整数中均匀随机选择的。随机性条件不适用于样本测试用例,但您的解决方案也必须通过样本。

输出
对于每个测试用例,输出一行包含一个整数:您可以通过出售钻石赚取的最大美元数。

样本输入

1
4
2 3 1 5
6 3 2 4
3 5 1 4
5 2 4 1
3 2 5 1
2 4 3 5
1 2 3 4
4 3 5 3

样本输出

528

解题思路

fi,j,k表示从*(1, 1)*走到 (i, j),一路上收集了 k 个钻石时,钻石的单价最高能涨到多少,

ans= max(k × fn,n,k)

对于固定的 (i, j) 来说,考虑两个状态 fi,j,xf**i,j,y,其中 x < y,如果 fi,j,x ≤ fi,j,y,则

状态 fi,j,x 一定不可能发展为最优解,可以剔除。对于每个 (i, j),用列表按照 k 升序保存所有

状态,并剔除不可能成为最优解的状态即可。

随机数据下当 n = 100 时,单个 (i, j) 的有效状态的峰值 k 大约为几千。时间复杂度

O(n2k)。

代码

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
typedef vector<P>V;
const int N=105;
int Case,n,m,i,j,k,a[N][N],b[N][N];ll ans;V f[N][N];P pool[1000005];
inline void ext(const P&t){
  while(m&&pool[m].second<=t.second)m--;
  if(!m||pool[m].first<t.first)pool[++m]=t;
}
inline void merge(const V&A,const V&B,V&C){
  int ca=A.size(),cb=B.size(),i=0,j=0;
  m=0;
  while(i<ca&&j<cb)ext(A[i].first<B[j].first?A[i++]:B[j++]);
  while(i<ca)ext(A[i++]);
  while(j<cb)ext(B[j++]);
  C.resize(m);
  for(i=0;i<m;i++)C[i]=pool[i+1];
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d",&n);
    for(i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&a[i][j]);
    for(i=1;i<=n;i++)for(j=1;j<=n;j++)scanf("%d",&b[i][j]);
    f[1][1].resize(1);
    f[1][1][0]=P(a[1][1],b[1][1]);
    for(i=1;i<=n;i++)for(j=1;j<=n;j++){
      if(i==1&&j==1)continue;
      if(i==1)f[i][j]=f[i][j-1];
      else if(j==1)f[i][j]=f[i-1][j];
      else merge(f[i-1][j],f[i][j-1],f[i][j]);
      for(k=0;k<f[i][j].size();k++){
        f[i][j][k].first+=a[i][j];
        f[i][j][k].second+=b[i][j];
      }
    }
    ans=0;
    for(i=0;i<f[n][n].size();i++)ans=max(ans,1LL*f[n][n][i].first*f[n][n][i].second);
    printf("%lld\n",ans);
  }
}

1010 Road Discount

题目

问题描述
Byteland有n个城市,用1到n标记。 Byteland 交通建设局正计划在这些城市之间建设 n-1 条双向道路,使得每对不同的城市都通过这些道路直接或间接连接。

工程公司提供了 m 条可能的候选道路进行施工。第i个候选道路将花费ci美元,如果最终建成,将有一条道路直接连接第i个城市和第vi个城市。幸运的是,每条道路都有其折扣价,其中第 i 个为 di。

Byteland 交通建设局最多可以以折扣价购买 k 条道路。请编写一个程序来帮助交通建设局找到 k=0,1,2,…,n−1 的最便宜的解决方案。

输入
第一行包含一个整数 T (1≤T≤10),即测试用例的数量。对于每个测试用例:

第一行包含两个整数n和m(2≤n≤1000,n−1≤m≤200000),分别表示城市数和候选道路数。

以下m行每行包含四个整数ui、vi、ci和di(1≤ui,vi≤n,ui≠vi,1≤di≤ci≤1000),描述一条候选道路。

输出
对于每个测试用例,输出 n 行,其中第 i 行 (1≤i≤n) 包含一个整数,表示当 k=i-1 时构建 n-1 条道路的最便宜的总成本。

保证答案永远存在。

样本输入

1
5 6
1 2 1 1
2 3 2 1
2 4 3 2
2 5 4 3
1 3 5 3
4 5 6 1

样本输出

10
7
6
5
5

解题思路

将原始边作为白边,折扣边作为黑边,由于同一条边不可能选择两次,那么问题等价于求

包含恰好 k 条黑边的最小生成树。这是一个经典问题,令 f(k) 表示包含恰好 k 条黑边的最小

生成树的边权和,则 f(k) 是一个凸函数,求出 f(k) 的方法为:

• 选择参数 c,将每条黑边的边权都加上 c

• 求出修改边权后的图的最小生成树,令 sum(c) 为对应的边权和,l(c) 为最小生成树中使

用黑边数量的最小值,r(c) 为最小生成树中使用黑边数量的最大值。

• 二分找到合适的参数 c,满足 l(c) k r(c),则 f(k) = sum(c) k × c

由于边权在 [1*,* 1000] 之间,因此可以预处理出 c = 1000 . . . 1000 的所有信息,一共需要

O(c) 次最小生成树。注意到如果对黑边或者白边单独求最小生成树,则非树边不可能用到,

因此可以将边数缩减至 O(n)。

总时间复杂度 O(m log n + nc log n)。

代码

#include<cstdio>
#include<algorithm>
using namespace std;
typedef pair<int,int>P;
const int N=1005,M=200005,V=1000;
int Case,n,m,i,f[N];P fl[V*2+5];
struct E{int x,y,w;}a[M],b[M];
inline bool cmp(const E&a,const E&b){return a.w<b.w;}
int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
inline bool merge(int x,int y){
  if(F(x)==F(y))return 0;
  f[f[x]]=f[y];
  return 1;
}
inline void reduce(E*e){
  sort(e+1,e+m+1,cmp);
  for(int i=1;i<=n;i++)f[i]=i;
  for(int i=1,cnt=0;i<=m;i++)if(merge(e[i].x,e[i].y))e[++cnt]=e[i];
}
inline P call(int k){
  for(int i=1;i<=n;i++)f[i]=i;
  int A=1,B=1,sum=0,cnt=0;
  while(A<n&&B<n){
    if(a[A].w<=b[B].w+k){
      if(merge(a[A].x,a[A].y))sum+=a[A].w;
      A++;
    }else{
      if(merge(b[B].x,b[B].y))sum+=b[B].w+k,cnt++;
      B++;
    }
  }
  while(A<n){
    if(merge(a[A].x,a[A].y))sum+=a[A].w;
    A++;
  }
  while(B<n){
    if(merge(b[B].x,b[B].y))sum+=b[B].w+k,cnt++;
    B++;
  }
  return P(sum,cnt);
}
inline int ask(int k){
  for(int i=-V;i<=V;i++)if(fl[i+V].second<=k)return fl[i+V].first-k*i;
  return -1;
}
int main(){
  scanf("%d",&Case);
  while(Case--){
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
      scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].w,&b[i].w);
      b[i].x=a[i].x,b[i].y=a[i].y;
    }
    reduce(a);
    reduce(b);
    for(i=-V;i<=V;i++)fl[i+V]=call(i);
    for(i=0;i<n;i++)printf("%d\n",ask(i));
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值