2021-07-25

21牛客多校3B - Black and white

p r o b l e m : problem: problem:
  现在要涂黑一个 n ∗ m ( n , m ≤ 5000 ) n*m(n,m≤5000) nm(n,m5000) 的棋盘上,涂黑第 ( i , j ) (i,j) (i,j) 个格子需要 c ( i , j ) c(i,j) c(i,j) 的花费,并且对于任意两行两列交错形成的四个格子,如果其中的三个已经被涂黑,那么第四个可以免费涂黑,求最小代价

s o l u t i o n : solution: solution:
   首先从贪心的角度出发,我们按权值从小到大取格子,当这个格子满足条件时免费。但是如何去判断一个格子是否免费呢?暴力判断?看这个格子所在行列上是否已经有格子被涂黑?这些显然都不行,我们根据题意转换:设当前格子在 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),同行上有 ( x 0 , y ) (x_0,y) (x0,y),同列上有 ( x , y 0 ) (x,y_0) (x,y0),那么必须 ( x , y ) (x,y) (x,y) 已经被涂黑,这个格子才能免费。我们可以想到用并查集去维护这个联通关系,也就是行与列的联通关系, ( x , y ) (x,y) (x,y) 这个点涂黑代表着 x x x 行与 y y y 列的联通,且如果当前点的行列已经联通,我们显然就不需要花费了。

   那么这个问题显然变成了 n + m n+m n+m 个点( n n n m m m 列)联通的最小花费,且点 ( x , y ) (x,y) (x,y) 相当于一条连接 x x x y + n y+n y+n 权值为 c ( x , y ) c(x,y) c(x,y) 的边,那么我们可以用最小生成树来解决这个问题,并且由于是稠密图,我们用 p r i m prim prim 效果会更好,但是由于权值范围 [ 0 , 1 e 5 ) [0,1e5) [0,1e5) 我们可以直接以权值为动态数组下标存边,然后进行 k r u s k a l kruskal kruskal 算法判断连通性。

i d e a : idea: idea:
   这题的要点在于将问题转化到 ”连通块“ ”并查集“ 上,并且这类问题通常要有将点转化为边的思想,以这题为例,二维坐标上的点联通着 x , y x,y x,y 坐标,那么这就是一条连接两点的边

CF1519E - Off by One

p r o b l e m : problem: problem:
  在一个二维平面上,第 i i i 个点在坐标 ( x i , y i ) (x_i,y_i) (xi,yi) 位置,现在可以进行如下操作:

  • 选择两个点 a , b ( a ≠ b ) a,b (a≠b) a,b(a=b)
  • 将点 a a a ( x a , y a ) (x_a,y_a) (xa,ya) 移动至 ( x a + 1 , y a ) (x_a+1,y_a) (xa+1,ya) ( x a , y a + 1 ) (x_a,y_a+1) (xa,ya+1)
  • 将点 b b b ( x b , y b ) (x_b,y_b) (xb,yb) 移动至 ( x b + 1 , y b ) (x_b+1,y_b) (xb+1,yb) ( x b , y b + 1 ) (x_b,y_b+1) (xb,yb+1)
  • 删除点 a , b a,b a,b

  但是只有满足移动后的两点 a ′ , b ′ a',b' a,b ( 0 , 0 ) (0,0) (0,0) 三点都在一条直线上,才能进行此操作,并且删除后点将不能再进行任何操作,求可以进行的最多操作数,以及每次操作的对象

s o l u t i o n : solution: solution:
  三个点在一条直线上即 a , b a,b a,b 两点与 ( 0 , 0 ) (0,0) (0,0) 构成的直线的斜率相同。我们将每个点看作一条边,即连接斜率 y + 1 x y x + 1 \frac {y+1}x \frac y{x+1} xy+1x+1y 这两个点的一条边,那么最后构成了一张图,且我们要在这张图上找最多的对数,满足:两条边共用一个点,且每条边至多使用一次。

  这个问题的答案很显然,设一个连通块的边数为 m i m_i mi,那么答案就是 ∑ ⌊ m i 2 ⌋ \sum \lfloor \frac {m_i}2 \rfloor 2mi。但是去求方案,这是一个非常困难的问题,除非我们借助 d f s dfs dfs树。我们对于每个连通块构造 d f s dfs dfs树,接着我们发现这个问题在树上很好解决,对于结点 u u u,接着我们递归到子树 v v v中进行匹配,可以发现这个匹配的过程要么多出一条边,要么不多,使可能存在的多出来的那条边与 v v v 相连,那么一旦它确实剩余,我们就用函数返回并且将它与 e ( u , v ) e(u,v) e(u,v) 匹配,否则 e ( u , v ) e(u,v) e(u,v) 则剩余出来。可以发现这个过程至多会产生一条多余的边,我们只需要用一个变量保存这条边的编号,接着十分方便 e ( u , v i ) e(u,v_i) e(u,vi) 之间的匹配,如果最后产生一条我们就从函数返回出去。我们处理完了树边,那么对于其他非树边,我们可以将它定向为走向叶节点的边,那么可以发现这些边走向的子树已经在刚刚被我们匹配完成了,这条边必然剩余,也可以看作上述树边匹配过程时子树全匹配的边 e ( u , v ) e(u,v) e(u,v),也就是说完全可以当作树边处理,只是不用再去递归这条边的子树,至此我们就 O ( n ) O(n) O(n) 完成了整个操作。

c o d e : code: code:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> pll;

const int maxn=2e5+10;

ll a[maxn],b[maxn],c[maxn],d[maxn];
int n,cnt,vis[maxn<<1];
int head[maxn],to[maxn<<1],nex[maxn<<1],idx[maxn<<1],tot=0;

inline void add(int u,int v,int id){
    to[++tot]=v;
    nex[tot]=head[u];
    idx[tot]=id;
    head[u]=tot;
}

pll f(ll x,ll y){
    ll g=__gcd(x,y);
    return pll(x/g,y/g);
}

map<pll,int> mp;
vector<int> ans;

int dfs(int u,int fa){
    int left=0;
    vis[u]=1;
    for(int i=head[u];i;i=nex[i]){
        int v=to[i];
        if(v==fa) continue;
        if(vis[v]==1) continue;
        if(!vis[v]){
            int temp=dfs(v,u);
            if(temp){
                ans.push_back(temp);
                ans.push_back(idx[i]);
            }
            else{
                if(left) ans.push_back(left),ans.push_back(idx[i]),left=0;
                else left=idx[i];
            }
        }
        else{
            if(left) ans.push_back(left),ans.push_back(idx[i]),left=0;
            else left=idx[i];
        }
    }
    vis[u]=2;
    return left;
}

int main(){
    cin>>n;
    int u,v;
    for(int i=1;i<=n;i++){
        scanf("%lld %lld %lld %lld",&a[i],&b[i],&c[i],&d[i]);
        pll x=f((a[i]+b[i])*d[i],b[i]*c[i]);
        pll y=f(a[i]*d[i],(c[i]+d[i])*b[i]);
        if(!mp[x]) mp[x]=++cnt;
        if(!mp[y]) mp[y]=++cnt;
        u=mp[x],v=mp[y];
        add(u,v,i);
        add(v,u,i);
    } 

    for(int i=1;i<=cnt;i++){
        if(vis[i]) continue;
        dfs(i,0);
    }
    cout<<ans.size()/2<<endl;
    for(int i=0;i<ans.size();i+=2) printf("%d %d\n",ans[i],ans[i+1]);
    return 0;
}

i d e a : idea: idea:
   一开始看到题,产生了从某个点开始然后尽量去匹配另外点的想法,但是显然这种贪心的匹配方法存在纰漏,即如果不知道往哪个方向移动且不知道用哪个点来和它匹配。这题和上一题一样,都需要把一些不显而易见的问题转化为图论上的问题,并且都是在二维平面上以点作边来建图,形成某个模型,再用我们熟悉的算法来解决。同时这道题的 d f s dfs dfs树 作为图论的一把利刃在很多方面如二分图,连通图问题上也还有很多用武之地,有待继续深究。

21牛客多校3E - Math

p r o b l e m : problem: problem:
  求满足 x y + 1 ∣ x 2 + y 2 , 1 ≤ x ≤ y ≤ n xy+1 | x^2+y^2,1≤x≤y≤n xy+1x2+y2,1xyn ( x , y ) (x,y) (x,y) 的对数 ( n < = 1 e 18 ) (n<=1e18) (n<=1e18)

s o l u t i o n : solution: solution:
   设 x 2 + y 2 = k ( x y + 1 ) x^2+y^2=k(xy+1) x2+y2=k(xy+1),我们如果枚举 x x x,即固定一个 x x x,那么得到 y y y 的方程 y 2 − k x y + x 2 − 1 y^2-kxy+x^2-1 y2kxy+x21,根据韦达定理: y 1 + y 2 = b − a = k x y_1+y_2=\frac b{-a}=kx y1+y2=ab=kx,那么 ( x , k x − y ) (x,kx-y) (x,kxy) 也是一组解。

  接着我们通过打表 得到通解都形如 ( x , x 3 ) (x,x^3) (x,x3),得到了此时 k = x 2 k=x^2 k=x2 。如果 ( x , y ) (x,y) (x,y) 是满足题意的一组解显然 x < = y x<=y x<=y,那么 x > k x − y x>kx-y x>kxy,即第二组解 ( x , k x − y ) (x,kx-y) (x,kxy) 不满足条件。可以按照这个特点反向构造出另一组解,通过 ( y , x ) − > ( y , k y − x ) (y,x)->(y,ky-x) (y,x)>(y,kyx),显然 ( y , k y − x ) (y,ky-x) (y,kyx) 满足条件 y < k y − x y<ky-x y<kyx,那么我们可以按照上述过程构造: ( x , x 3 ) − > ( x 3 , x 5 − x ) . . . . (x,x^3)->(x^3,x^5-x).... (x,x3)>(x3,x5x)....,然后把一组解的第二关系放入数组排序后二分即可

  • 2020-07-26
  • 2020-07-28
  • 2020-08-01
  • 2020-08-25
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值