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

总结

今天这把杭电经典坐牢局,开局火速签到完之后开始长达 3 个小时的罚坐,想破脑袋也没有把1007和1005的其中一道开出来。

题解

1001 - Theramore

题意:

一个 01 序列,可以任意翻转奇数长度的区间,求能达到的最小字典序。

做法:

由于我们只能对奇数长度的区间进行反转操作,我们只需贪心地考虑将下标为奇数/偶数位置的 1 全部放在后面即可。

代码:(赛时代码,有点丑)

/*
 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--)
    {
        string ss;
        cin>>ss;
        int j1=0,j0=0,o1=0,o0=0;
        for(int i=0;i<ss.size();i++)
        {
            if(i%2==0)
            {
                if(ss[i]=='1')
                    o1++;
                else
                    o0++;
            }
            else
            {
                if(ss[i]=='1')
                    j1++;
                else
                    j0++;
            }
        }
        string s1,s2;
        for(int i=0;i<o0;i++)
            s1+='0';
        for(int i=0;i<o1;i++)
            s1+='1';
        for(int i=0;i<j0;i++)
            s2+='0';
        for(int i=0;i<j1;i++)
            s2+='1';
        int idx=0;
        while(idx<j1+j0)
        {
            cout<<s1[idx];
            cout<<s2[idx++];
        }
        if(idx<o1+o0)
            cout<<s1[idx];
        cout<<endl;
    }
    return 0;
}

1004 - Quel’Thalas

题意:

在二维平面上,将 [ 0 , n ] ∗ [ 0 , n ] [0,n]*[0,n] [0,n][0,n] 的整点中去掉 ( 0 , 0 ) (0,0) (0,0) 之后最少需要多少条直线使得每个点至少有一条边覆盖(直线不能经过 ( 0 , 0 ) (0,0) (0,0)

做法:

签到题,输出 2 ∗ n 2*n 2n 即可

代码:

/*
 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;
        cout<<2*n<<endl;
    }
    return 0;
}

1005 - Ironforge

题意:

一条链,每个点上有一个数 a i a_i ai ,每条边上有一个质数 b i b_i bi 。一开始在某个点上,有一个空背包,走到一个点上可以把它的质因子放进背包,一条边如果背包里有那个质数就可以走。多组询问求从 x x x 出发能否走 到 y y y (即求每个点能走到的最大范围)。

做法:

我们考虑将从每个点开始能到达的最大范围预处理出来,然后 O 1 O1 O1 查询,先用埃筛将从 1 − n 1-n 1n 每个数的最小质因子处理出来,然后先从后往前扫一遍,处理出从 i i i 开始能到达的最右点,如果 i i i 能到达 i + 1 i+1 i+1 ,则取 i + 1 i+1 i+1 的范围并继续向右查看能否到达更右的点;

再从前往后扫,求出每个点能到达的最大范围,这时我们只需从左右两边跳,看是否能达到更远的点即可,左右跳的时间复杂度是均摊 O 1 O1 O1 的。

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll 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;
const int maxn = 200010;
int minp[maxn];
void init()
{
    for(int i=2;i<=maxn-10;i++)
    {
        if(minp[i]) continue;
        for(int j=i;j<=maxn-10;j+=i)
            if(!minp[j])
                minp[j]=i;
    }
}
vector<int> g[maxn];
int b[maxn];
int l[maxn],r[maxn];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    init();
    cin>>t;
    while(t--)
    {
        cin>>n>>k;
        for(int i=1;i<=n;i++)
        {
            g[i].clear();
            l[i]=r[i]=i;
        }
        for(int i=1;i<=n;i++)
        {
            cin>>m;
            while(m>1)
            {
                int x=minp[m];
                while(m%x==0)
                    m/=x;
                g[x].emplace_back(i);
            }
        }
        for(int i=1;i<n;i++)
            cin>>b[i];
        for(int i=n-1;i>=1;i--)
        {
            while(r[i]<n)//向右跳
            {
                int nb=b[r[i]];
                auto it=lower_bound(g[nb].begin(),g[nb].end(),l[i]);
                if(it!=g[nb].end()&&*it<=r[i])
                    r[i]=r[r[i]+1];
                else
                    break;
            }
        }
        for(int i=1;i<=n;i++)
        {
            while(1)
            {
                bool flag=false;
                while(l[i]>1)//左右跳
                {
                    int nb=b[l[i]-1];
                    auto it=lower_bound(g[nb].begin(),g[nb].end(),l[i]);
                    if(it!=g[nb].end()&&*it<=r[i])
                    {
                        l[i]=l[l[i]-1];
                        flag=1;
                    }
                    else
                        break;
                }
                while(r[i]<n)
                {
                    int nb=b[r[i]];
                    auto it=lower_bound(g[nb].begin(),g[nb].end(),l[i]);
                    if(it!=g[nb].end()&&*it<=r[i])
                    {
                        r[i]=r[r[i]+1];
                        flag=1;
                    }
                    else
                        break;
                }
                if(!flag)
                    break;
            }
        }
        while(k--)
        {
            int u,v;
            cin>>u>>v;
            if(l[u]<=v&&v<=r[u])
                cout<<"Yes"<<endl;
            else
                cout<<"No"<<endl;
        }
    }   
    return 0;
}

1007 - Darnassus

题意:

给出一个排列 p p p ,把每个位置视为点,建一个无向图, 之间的边权为 ∣ i − j ∣ ∗ ∣ p [ i ∣ − p [ j ] |i-j|*|p[i|-p[j] ijp[ip[j] ,求这个图的最小生成树。

做法:

由于这题的数据范围是 5 e 4 5e4 5e4 ,所以我们不能用暴力的方法建边跑克鲁斯卡尔最小生成树,我们换一种思路,考虑到如果我们将点 i i i 与点 i + 1 i+1 i+1 连边,那么我们就会得到一棵最大边权为 n − 1 n-1 n1 的最小生成树( ∣ i − j ∣ = 1 , ∣ p [ i ] − p [ j ∣ < = n − 1 |i-j|=1,|p[i]-p[j|<=n-1 ij=1,p[i]p[j<=n1),由克鲁斯卡尔算法的原理我们可以知道这张图中存在一棵最小生成树,使得这棵树最大的边权 < = n − 1 <=n-1 <=n1 ,那么我们尝试寻找这些边,由于我们要找到 ∣ i − j ∣ ∗ ∣ p [ i ∣ − p [ j ] ∣ < = n − 1 |i-j|*|p[i|-p[j]|<=n-1 ijp[ip[j]<=n1 的边,所以这要求 ∣ i − j ∣ |i-j| ij ∣ p [ i ] − p [ j ] ∣ |p[i]-p[j]| p[i]p[j] 之中至少有一个满足 < = n − 1 <=\sqrt{n-1} <=n1 ,因此我们就可以根据题目所给数据的性质在 O ( n n ) O(n\sqrt n) O(nn ) 的复杂度内找到这些边,并使用类似桶排序的方法从小到大遍历这些边构成最小生成树。

代码:

/*
 author:wuzx
 */

#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
#define P pair<int,int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
int t;
int n,m,k;
const int maxn = 50010;
vector<P> g[maxn];
int a[maxn],pos[maxn],fa[maxn];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>t;
    while(t--)   
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            pos[a[i]]=i;
            fa[i]=i;
            g[i].clear();
        }
        m=sqrt(n);
        int num1,num2,u,v;
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n&&j<=i+m;j++)
            {
                num1=(j-i)*abs(a[i]-a[j]);
                num2=(j-i)*abs(pos[i]-pos[j]);
                if(num1<n)
                    g[num1].emplace_back(i,j);
                if(num2<n)
                    g[num2].emplace_back(pos[i],pos[j]);
            }   
        }
        int ans=0,cnt=n-1;
        for(int i=1;i<=n;i++)
        {
            for(auto j:g[i])
            {
                tie(u,v)=j;
                u=find(u);
                v=find(v);
                if(u!=v)
                {
                    fa[u]=v;
                    ans+=i;
                    cnt--;
                }
                if(!cnt)
                    break;
            }
            if(!cnt)
                break;
        }
        cout<<ans<<endl;
    }
    return 0;
}

1011 - Stormwind

题意:

一个 n × m n\times m n×m 的长方形,可以沿水平或竖直方向画若干条线,每条线的两端点都在长方形的边界上。要求这些线划分出的每个小长方形面积都 > = k >=k >=k ,求最多可以画几条线。

做法:

枚举小长方形的某个边长的最小值,由此可以求出另一个边长允许的最小值,然后求出两个方向分别最多能画几条线。

代码:

/*
 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>>m>>k;
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int res=0;
            if(i*m<k)
                continue;
            res=n/i-1;
            int num=(k+i-1)/i;
            res+=m/num-1;
            ans=max(ans,res);
        }
        cout<<ans<<endl;
    }   
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值