2022杭电多校6(总结+补题)

本文作者回顾了一场算法竞赛的表现,总结了开局策略不当导致的问题,如过于依赖队友思路、忽视其他题目等。文章详细解析了三道算法题目的解题思路,包括树形DP、几何变换和图论问题,提供了相应的代码实现,并指出了解题的关键点和技巧。
摘要由CSDN通过智能技术生成
总结

这把应该是多校以来打得最差的一把了,开局我和队友1签了1006,又把1012讨论出来之后,就跟队友2看计算几何去了,队友2看了一会提出一个“看上去正确但贼复杂的做法”,然后我就听信了他的谗言开写,写了一个多小时之后发现写挂了,就开始了疯狂debug,最后写好之后发现方法假了,但队友2一直坚持自己是对的(他不知道计算几何做这么多操作会失精度),于是我就陪他一直在这道题上磨到了最后,也没有看其他题,队友1期间也一直在自闭,总的来说,这场的策略问题很大,不应该因为队友的一个思路就一直死做一个题不看其他,也不应该在签完到之后就跑去死磕不熟练的计算几何,一场打完大家心态都崩了。

题解

1006 - Maex

题意:

给定一棵由从 1 1 1 n n n 编号的 n n n 个顶点组成的有根树,根为顶点 1 1 1 。顶点 i i i 有一个自然数权重 a i a_i ai,并且没有两个不同的顶点具有相同的权重(这个权重由我们自己分配)。定义一个点的 b u = M E X { x   ∣   ∃ v ∈ s u b t r e e ( u ) , x = a v } b_u=MEX\left \{ x \space | \space \exists v \in subtree(u),x=a_v \right \} bu=MEX{x  vsubtree(u),x=av} ,即一个点的 b b b 值等于其子树(包括它自己)所有权重集合的 M E X MEX MEX 值,问 ∑ i = 1 n b i \sum_{i=1}^{n} b_i i=1nbi 的最大值是多少。

做法:

M E X MEX MEX 的性质和观察我们可以知道,一个节点的 b i b_i bi 值为其子树中所有节点的个数, ∑ i = 1 n b i \sum_{i=1}^{n} b_i i=1nbi 的值即为一条链中所有节点的 b i b_i bi 值之和

,因此我们可以采用树形 d p dp dp 的方法, d p [ u ] dp[u] dp[u] 表示子树 s u b t r e e ( u ) subtree(u) subtree(u) s u m ( b i ) sum(b_i) sum(bi) 能达到的最大值, s o n son son 数组记录 u u u 节点子树节点的个数,那么就有 d p dp dp 方程:

d p [ u ] = s o n [ u ] + m a x ( d p [ v ] ) ( v ∈ s u b t r e e ( u ) ) dp[u]=son[u]+max(dp[v])(v\in subtree(u)) dp[u]=son[u]+max(dp[v])(vsubtree(u))

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>n;
        vector<vector<int>> g(n);
        for(int i=1;i<n;i++)
        {
            int u,v;
            cin>>u>>v;
            u--;v--;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        vector<int> dp(n,0);
        function<int(int,int)> dfs = [&](int now,int fr)
        {
            int son=1;
            int ma=0;
            for(int x:g[now])
            {
                if(x==fr)
                    continue;
                son+=dfs(x,now);
                ma=max(ma,dp[x]);
            }
            dp[now]=son+ma;
            return son;
        };
        dfs(0,-1);
        cout<<dp[0]<<endl;
    }   
    return 0;
}

1009 - Map

题意:

给你一个长方形以及这个长方形按比例 k ( 0 < k < 1 ) k(0<k<1) k(0<k<1) 缩小后的小长方形,将两长方形的 A − a   , B − b A-a\space ,B-b Aa ,Bb 相连形成的直线相交于点 P P P ,再将小长方形围绕点 P P P 旋转任意角度,现给出原长方形的四个顶点 A   B   C   D A\space B \space C \space D A B C D 以及旋转后小长方形的四个顶点 a   b   c   d a\space b \space c \space d a b c d ,求点 P P P 的坐标。

做法:

我们假设点 P ,由于在长方形中有 A B ⊥ A D AB \perp AD ABAD , 因此可设

O P ⃗ = O A ⃗ + λ A B ⃗ + μ A D ⃗ \vec{OP}=\vec{OA}+\lambda \vec{AB}+ \mu \vec{AD} OP =OA +λAB +μAD

其中 λ , μ \lambda,\mu λ,μ 为待定参数,又因为点 P P P 在两个长方形中的对应关系相同,因此又有:

O P ⃗ = O a ⃗ + λ a b ⃗ + μ a d ⃗ \vec{OP}=\vec{Oa}+\lambda \vec{ab}+ \mu \vec{ad} OP =Oa +λab +μad

联立上式可得:

λ ( A B ⃗ − a b ⃗ ) + μ ( A D ⃗ − a d ⃗ ) = O a ⃗ − O A ⃗ \lambda(\vec{AB}-\vec{ab})+\mu(\vec{AD}-\vec{ad})=\vec{Oa}-\vec{OA} λ(AB ab )+μ(AD ad )=Oa OA

解出 λ , μ \lambda,\mu λ,μ 后带入一式可得 P P P 的坐标

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
struct point{
    double x,y;
};
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>t;
    int a1,a2,b1,b2,c1,c2,d1,d2;
    point A,B,C,D,a,b,c,d;
    while(t--)
    {
        cin>>a1>>a2>>b1>>b2>>c1>>c2>>d1>>d2;
        A.x=a1;A.y=a2;B.x=b1;B.y=b2;C.x=c1;C.y=c2;D.x=d1;D.y=d2;
        cin>>a1>>a2>>b1>>b2>>c1>>c2>>d1>>d2;
        a.x=a1;a.y=a2;b.x=b1;b.y=b2,c.x=c1;c.y=c2;d.x=d1;d.y=d2;
        double x1=B.x-A.x-b.x+a.x;
        double y1=B.y-A.y-b.y+a.y;
        double x2=D.x-A.x-d.x+a.x;
        double y2=D.y-A.y-d.y+a.y;
        double AA=a.x-A.x;
        double BB=a.y-A.y;
        double u=(BB*x2-AA*y2)/(x2*y1-x1*y2);
        double r=(BB*x1-AA*y1)/(x1*y2-x2*y1);
        cout<<fixed<<setprecision(8)<<A.x+u*(B.x-A.x)+r*(D.x-A.x)<<" "<<
        A.y+u*(B.y-A.y)+r*(D.y-A.y)<<endl;
    } 
    return 0;
}

1010 - Planar graph

题意:

给你一个平面图,图中一些边,每条边的编号为题目给出的顺序,问最少要消除那些边,使得原来平面图中的平面全部连通,给出字典序最小的消除边的解。

做法:

由于我们要消去一些边使原图中的平面两两连通,我们可知在消去边之后原来的图中不存在环,那么消去边后的图应该是一棵树,又因为我们要给出消去边字典序最小的解,因此我们可以把边的序号作为边权,构造一棵最大生成树,未被使用的边即为字典序最小的要消除的边。

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
struct dsu{
    const int nn;
    vector<int> fa;
    dsu(int n1):nn(n1),fa(n+1)
    {
        for(int i=0;i<=n;i++)
            fa[i]=i;
    }
    int find(int x)
    {
        return fa[x]==x?x:fa[x]=find(fa[x]);
    }
    void merge(int x,int y)
    {
        int aa=find(x),bb=find(y);
        if(aa!=bb)
            fa[aa]=bb;
    }
};
#define tp3 tuple<int,int,int> 
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>n>>m;
        dsu ds(n);
        vector<tp3> a;
        for(int i=1;i<=m;i++) 
        {
            int u,v;
            cin>>u>>v;
            a.emplace_back(i,u,v);
        }
        function<bool(tp3,tp3)> cmp = [&](tp3 aa,tp3 bb)
        {
            return get<0>(aa)>get<0>(bb);
        };
        sort(a.begin(),a.end(),cmp);
        vector<int> vis(m+1,0);
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            tie(w,u,v)=a[i];
            if(ds.find(u)!=ds.find(v))
            {
                ds.merge(u,v);
                vis[w]=1;
            }
        }
        int ans=count(vis.begin()+1,vis.end(),0);
        
        cout<<ans<<endl;
        for(int i=1;i<=m;i++)
            if(!vis[i])
                cout<<i<<" ";
        cout<<endl;
    }
    return 0;
}

1012 - Loop

题意:

给你一个数组 a a a ,你可以对数组 a a a k k k 次以下操作:

选定 [ l , r ] [l,r] [l,r] ,将 a [ l , … , r ] a[l,…,r] a[l,,r] 中所有元素向左移动一个单位,移动后 a [ l ] a[l] a[l] a [ r ] a[r] a[r] 的位置上,问:在 k k k 次操作后,输出字典序最大的数组 a a a

做法:

考虑贪心做法,每次移动我们都把当前剩余序列中第一个 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1] 中第一个 a [ i ] a[i] a[i] 放入优先队列 q q q 中(这里找第一个 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1] 使用的是用并查集实现的链表,每次删除当前元素就用前一个元素指向后一个元素),用完 k k k 次操作或没有满足条件的 a [ i ] a[i] a[i] 时,我们开始将优先队列中的数插入回原序列中(也就是将 a [ l ] a[l] a[l] 放到原来 a [ r ] a[r] a[r] 的位置),在原序列中从头开始,如果 a [ i ] < q . t o p ( ) a[i] < q.top() a[i]<q.top() ,则将优先队列的堆首元素插入到 a[i] 前面,否则 i i i++ ,原剩余序列如果遍历完,就按顺序将优先队列中元素放到原序列后方,若优先队列中无元素,就直接把构造好的序列输出即可。

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll long long
// #define int long long
#define endl "\n"
#define P pair<int,int>
#define f first
#define s second
using namespace std;
const int inf = 0x3f3f3f3f;
int t;
int n,m,k;
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        vector<int> a(n+1),pre(n+1),bak(n+2);
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            pre[i]=i;
            bak[i]=i;
        }
        bak[n+1]=n+1;
        priority_queue<int> q;
        function<int(int)> find1 = [&](int x)
        {
            return pre[x]==x?x:pre[x]=find1(pre[x]);
        };
        function<int(int)> find2 = [&](int x)
        {
            return bak[x]==x?x:bak[x]=find2(bak[x]);
        };
        int idx=1;
        int fst=inf;
        while(k)
        {
            int now=find2(idx);
            if(now==n)
                break;
            int nxt=find2(now+1);
            if(a[now]<a[nxt])
            {
                q.push(a[now]);
                // cout<<a[now]<<" "<<now<<endl;
                k--;
                fst=min(fst,now);
                int n1=now;
                bak[now]=nxt;
                if(find1(now-1)!=0)
                {
                    pre[find1(now)]=find1(now-1);
                    idx=pre[now];
                }
                else
                    idx=bak[now];
            }
            else
                idx=nxt;
        }
        int now=find2(1);
        vector<int> ans;
        while(now<fst&&now<=n)
        {
            // cout<<"out "<<a[now]<<endl;
            ans.push_back(a[now]);
            now=find2(now+1);
        }
        now=find2(now);
        while(!q.empty())
        {
            if(a[now]>=q.top())
            {
                ans.push_back(a[now]);
                now=find2(now+1);
            }
            else
            {
                ans.push_back(q.top());
                q.pop();
            }
            if(now==n+1)
                break;
        }
        while(!q.empty())
        {
            ans.push_back(q.top());
            q.pop();
        }
        while(now<=n)
        {
            ans.push_back(a[now]);
            if(now==n)
                break;
            now=find2(now+1);
        }
        for(int i=0;i<n;i++)
            cout<<ans[i]<<" \n"[i==n-1];
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值