2024牛客暑期多校训练营第二场补题报告(超详细)

补题报告-第二场

赛时解决题目:C,E,H,I,B,

解题思路


                代码块还没用熟悉,如果要用就委屈下自己复制黏贴然后格式化一下

目录

C:Accept

E:Accept

思路:

H:Accept

题意:

       思路:

I:Accept

B:Accept

A:


C:Accept

题意:给两行R,W字符串,任选起始点,上下左右走,能走过的R字符最多有多少个(不包括起始点R,且路径必须只能是R字符)。

思路:根据路径不分首尾,并且只有两行,所以我们可以简单认为从左往右走和从有往左走是等价的,只需列出从左往右的答案即可。

以s[1][1]和s[2][1]分别作为起始点出发,如果其他路上只有一个R,那很明显只有一条路,如果y+1是两个R,则考虑要不要走对角线,也可以发现,如果当前s[3-i][j]如果也是R,那么走对角线是更优的,因为到下一个列时,你所经过的R多了一个。

用dfs跑,为了防止重复跑,写个vis数组,也是避免对角线被自己循环卡死,重复访问。

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e7+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
ll n;
string s[3];
ll ans=0;
vector<vector<bool>>vis(3,vector<bool>(N,0));
vector<vector<bool>>vis2(3,vector<bool>(N,0));

void dfs2(ll x,ll y,ll cnt)
{
    if(x>2||x<1||y>n||y<1)return;
    if(vis2[x][y]||s[x][y]!='R')return;
    cnt++;
//    cout<<x<<' '<<y<<' '<<cnt<<'\n';
    vis2[x][y]=1;
    ans=max(ans,cnt-1);
    if(x==1)
    {
        if(y+1<=n&&s[x][y+1]=='R'&&s[x+1][y]=='R'&&s[x+1][y+1]=='R'&&!vis2[x+1][y])
        {
            dfs2(x+1,y,cnt);
        }
        else if(y+1<=n&&s[x][y+1]=='R')dfs2(x,y+1,cnt);
        else if(s[x+1][y]=='R'&&!vis2[x+1][y])dfs2(x+1,y,cnt);
    }
    else
    {
        if(y+1<=n&&s[x][y+1]=='R'&&s[x-1][y]=='R'&&s[x-1][y+1]=='R'&&!vis2[x-1][y])
        {
            dfs2(x-1,y,cnt);
        }
        else if(y+1<=n&&s[x][y+1]=='R')dfs2(x,y+1,cnt);
        else if(s[x-1][y]=='R'&&!vis2[x-1][y])dfs2(x-1,y,cnt);
    }
};
void solve()
{
    cin>>n;
    cin>>s[1]>>s[2];
    s[1]='.'+s[1];
    s[2]='.'+s[2];
    for(int j=1; j<=n; j++)
    {
        for(int i=1; i<=2; i++)
        {
            queue<tuple<ll,ll,ll>>q,p;
            function<void(ll,ll,ll)>dfs=[&](ll x,ll y,ll cnt)
            {
                if(x>2||x<1||y>n||y<1)return;
                if(vis[x][y]||s[x][y]!='R')return;
                cnt++;
//                cout<<x<<' '<<y<<' '<<cnt<<'\n';
                vis[x][y]=1;
                ans=max(ans,cnt-1);
                if(x==1)
                {
                    if(y+1<=n&&s[x][y+1]=='R'&&s[x+1][y]=='R'&&s[x+1][y+1]=='R'&&!vis[x+1][y])
                    {
                        dfs(x+1,y,cnt);
                    }
                    else if(y+1<=n&&s[x][y+1]=='R')dfs(x,y+1,cnt);
                    else if(s[x+1][y]=='R'&&!vis[x+1][y])dfs(x+1,y,cnt);
                }
                else
                {
                    if(y+1<=n&&s[x][y+1]=='R'&&s[x-1][y]=='R'&&s[x-1][y+1]=='R'&&!vis[x-1][y])
                    {
                        dfs(x-1,y,cnt);
                    }
                    else if(y+1<=n&&s[x][y+1]=='R')dfs(x,y+1,cnt);
                    else if(s[x-1][y]=='R'&&!vis[x-1][y])dfs(x-1,y,cnt);
                }
            };
            if(s[i][j]=='R')dfs(i,j,0);
            if(s[3-i][j]=='R')
                dfs2(3-i,j,0);
        }
    }
    cout<<ans<<'\n';
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
//    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。

E:Accepta

题意:对于题目所给的n,找出一个小于n的数字,使得它与n的异或和,与它和n的gcd相等。

思路:

由于n(1e18)特别大,所以不考虑遍历,应该考虑二进制或者二分,二分没有找到可行性的思路,便考虑二进制,很容易发现,对于高位的二进制,必然是低位二进制的倍数,例如4是2倍数,4是2的更高位。考虑到异或其实是找出不相同的位,使其变成1,其余的变成0。思考一下,会发现其实n的二进制表达式中,最低位的1,必然是n的因子,如果要异或得到这个1,会发现很容易得到,只需要构造和n这个数只在最低位的二进制为1的为不相同便可。所以答案便是n异或它最低位的1,最后得到的数不能是0。即可。

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨

void solve()
{
    ll x;cin>>x;
    vector<ll>vec;
    ll xx=x;
    while(xx)
    {
        vec.pb(xx&1);
        xx>>=1;
    }
    ll ret=0,k=0;
    for(int i=0;i<vec.size()-1;i++)
    {
        if(vec[i])k++;
        if(vec[i]&&(!ret))
        {
            vec[i]=0;
            ret=1;
        }
    }
    ll ans=0;
    ll now=1;
    for(int i=0;i<vec.size();i++)
    {
        ans+=vec[i]*now;
        now<<=1;
    }
    if(k)cout<<ans<<'\n';
    else cout<<-1<<'\n';
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。

H:Accept

题意:

对于一个上下左右指令的字符串,问其中有多少个连续的字串经过指定的x,y点。

       思路:

仔细观察题目,发现题目给的路径信息是经过,那么只要前缀子串经过该点,后续字串便可以随便加,不影响经过的结果。

也就是说,我们只需找到前缀字串并找到它的末尾节点即可。

枚举前缀字串的起始点i,为了在时间复杂度的要求下计算是否后续路径经过答案点,则需要开map<PLL,vector<ll>>记录最初始的长度为n的串最初都经过了那些点,并且这些点经过时的下标(因为比i前的下标所经过的点不可使用)。

对于查找以i为起始点的前缀的并经过终点的字串末尾的坐标,我们可以通过map查询,遍历到i时,终点相当于偏移了,偏移到x+(i前面那些指令所改变的x方向),y+(i前面那些指令所改变的y方向)。用map查询是否存在该点,如果存在,二分出对应的vector内第一个大于等于i的下标,得到该数字,就是答案,如果找不到则contine,让ans加上n-mp[{xx+nowx,yy+nowy}][l]+1;即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair <ll,ll> pii;
const ll inff = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int N = 2e5+5;
const int mod = 998244353;
char c[N];
map <pii, vector <ll> > mp;
void solve()
{
    ll n,tox,toy;
    cin >> n >> tox >> toy;
    ll step = 0;
    ll x = 0, y = 0;
    for(int i=1; i<=n; i++)
    {
        cin >> c[i];
        if(c[i] == 'W')
            y ++;
        else if(c[i] == 'S')
            y --;
        else if(c[i] == 'A')
            x --;
        else
            x ++;
        mp[ {x,y}].push_back(i);
    }


    if(tox == 0 && toy == 0)
    {
        cout << n * (n + 1) / 2 << "\n";
        return;
    }

    ll ans = 0;
    ll tx=0,ty=0;

    for(int i=1; i<=n; i++)
    {
        if(c[i] == 'W')
            ty ++;
        else if(c[i] == 'S')
            ty --;
        else if(c[i] == 'A')
            tx --;
        else
            tx ++;
        ll dx=tx+tox,dy=ty+toy;
        ll step =lower_bound(mp[ {dx,dy}].begin(), mp[ {dx,dy}].end(), i) - mp[ {dx, dy}].begin();
        if(step>0&&step<mp[{dx,dy}].size()&&mp[ {dx,dy}][step]>=i)
            ans += (n - mp[ {dx,dy}][step]+1);
    }
    
    cout << ans << "\n";
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    solve();
    return 0;
}

I:Accept

题意:给一串长度为2*n的数字,1~n分别出现两次,你可以进行多次消除,消除规则是选择相同的两个数字,将这两个数字围起来的总长度*上这个数字,便是本次消除的价值,问消除的最大价值是多少。

解题思路:我们会发现如果先处理大数,那么会受到交错区间的影响,所以转换思路,应该先从小区间算起。

所以我们开设dp数组,记录消除到某个区间右端点时的暂时最大价值。

再开一个tmp数组记录当前点为尾节点的临时最大值(我确实想不到)

用pos数组记录每个值对应的左端点和右端点。

因为要按照小区间优先规则,所以再额外开设一个up数组,并按照区间长度排序(右端点与左端点的差值)。

之后便可以for(auto [l,r]:up)从小到大遍历区间

遍历这个区间,会发现当前点tmp[l]的暂时最大值就是a[l],遍历时,从l+1->r

会发现tmp的临时最大值便是tmp[i-1]+a[l],检查当前点a[i]的左端点是否在本次遍历

的区间内,如果在里面,说明要判断哪个更优。

tmp[i]=tmp[i-1]+a[l];

            ll left=pos[a[i]].ff;

            if(left!=i&&left>l)

            {

                tmp[i]=max(tmp[i],tmp[left-1]+dp[i]);///tmp[left-1]便是上一次合成区间得到的答案,也就是前序区间的答案。

            }

遍历之后,让dp[r]=tmp[r]便可以得到以r尾节点的最优值,但发现这并不会遍历到nn+1这个范围,所以我们需要在一开始的pos里面添加{0,n*2+1}这个区间。

最后输出dp[n+n+1]作为答案。

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e6+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
bool cmp(PLL &a,PLL &b)
{
    return a.ss - a.ff < b.ss - b.ff;      //长度比较
}
void solve()
{
    ll n;
    cin>>n;
    ll nn=2*n;
    vector<ll>a(nn + 2);
    vector<PLL>pos(n + 1);
    for(int i=1;i<=nn;i++)
    {
        cin>>a[i];
        if(pos[a[i]].ff)pos[a[i]].ss=i;
        else pos[a[i]].ff=i;
    }
    vector<PLL>up(n+1);
    pos.push_back({0,nn+1});
    up=pos;
    sort(up.begin(),up.end(),cmp);
    vector<ll>tmp(nn+2);
    vector<ll>dp(nn+2);
    for(auto [l,r]:up)
    {
        tmp[l]=a[l];
        for(int i=l+1;i<=r;i++)
        {
            tmp[i]=tmp[i-1]+a[l];
            int left=pos[a[i]].ff;
            if(left!=i&&left>l)
            {
                tmp[i]=max(tmp[i],tmp[left-1]+dp[i]);
            }
        }
        dp[r]=tmp[r];
    }
    cout<<dp[nn+1]<<endl;
}



signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
//    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



B:

在补这道题时,复习了下最小生成树算法(克鲁斯卡尔算法和普里姆算法)

Kruskal算法,Prim算法,Borůvka算法

在这复习一下

小芝士:

图由两个集合V、E组成,定义为G=(V, E),其中V是顶点的有限非空集合,E是由V中顶点偶对表示的边的集合。

1:Kruskal算法:(加边法)(适用于稀疏图)时间复杂度mlogm(m是边)

将所有边根据权值排个序,由小排到大,接下来遍历这些边,选取

这条边的条件是这条边对应的两个点属于不同集合(即用并查集实现),选取后,

将两点归于同一集合中,直到所有点都属于同一个集合或者已选取n-1条边。

贴下洛谷代码:最小生成树【模板】

ll f[N];

struct node

{

    ll x,y,v;

    bool operator<(const node &a)const

    {

        return v<a.v;

    }

};

ll find(ll x)

{

    if(f[x]==x)return x;

    else return f[x]=find(f[x]);

}

void solve()

{

    ll n,m;

    cin>>n>>m;

    for(int i=1; i<=n; i++)

    {

        f[i]=i;

    }

    vector<node>q;

    for(int i=1; i<=m; i++)

    {

        ll x,y,v;

        cin>>x>>y>>v;

        q.push_back({x,y,v});

    }

    sort(q.begin(),q.end());

    ll sum=0;

    for(auto i:q)

    {

        ll x=find(i.x);

        ll y=find(i.y);

        if(x==y)continue;

        else

        {

            sum+=i.v;

            f[x]=y;

        }

    }

    ll cnt=0;

    for(int i=1; i<=n; i++)

    {

        if(f[i]==i)

        {

            cnt++;

            if(cnt==2)

            {

                cout<<"orz\n";///无法连通

                return;

            }

        }

    }

    cout<<sum<<'\n';

}

2:prim算法:(加点法)时间复杂度为mlogm+nlogn或者n^2(n为点数)

算法从一个顶点伊始,逐渐延申覆盖联通网。

实现:开一个数组:dist记录当前所维护的联通网距离各个点的距离。

初始dist[i]全是INF(不可能达到的无穷大)

以所需要的一点作为起始点,把加入的点距离它周围点的距离计算出来,与dist数组取min,选取距离最近的点并打上标记,但是时间是O(n^2)的但很明显,是n^2而跟m没多大关系。

void prim()///朴素prim算法,适用于稠密图

{

    dist[1] = 0;//把点1加入集合S,点1在集合S中,将它到集合的距离初始化为0

    book[1] = true;//表示点1已经加入到了S集合中

    for(int i = 2 ; i <= n ;i++)dist[i] = min(dist[i],g[1][i]);//用点1去更新dist[]

    for(int i = 2 ; i <= n ; i++)

    {

        int temp = INF;//初始化距离

        int t = -1;//接下来去寻找离集合S最近的点加入到集合中,用t记录这个点的下标。

        for(int j = 2 ; j <= n; j++)

        {

            if(!book[j]&&dist[j]<temp)//如果这个点没有加入集合S,而且这个点到集合的距离小于temp就将下标赋给t

            {

                temp = dist[j];//更新集合V到集合S的最小值

                t = j;//把点赋给t

            }

        }

        if(t==-1){res = INF ; return ;}

        //如果t==-1,意味着在集合V找不到边连向集合S,生成树构建失败,将res赋值正无穷表示构建失败,结束函数

        book[t] = true;//如果找到了这个点,就把它加入集合S

        res+=dist[t];//加上这个点到集合S的距离

        for(int j = 2 ; j <= n ; j++)dist[j] = min(dist[j],g[t][j]);//用新加入的点更新dist[]

    }

}

///堆优化版的prim算法,适用于稀疏图(感觉不如写Kruskal)

#include<bits/stdc++.h>

using namespace std;

typedef pair<int,int>PLL;

const int N=510,M=1e5+10;

int h[N],e[2*M],ne[2*M],w[2*M],idx;

int dist[N],n,m;

bool st[N];

void add(int a,int b,int c)

{

e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;

}

int prim()

{

int cnt=0,res=0;

priority_queue<pii,vector<pii>,greater<pii>>q;

q.push({0,1});

while(q.size())

{

auto t=q.top();q.pop();

int ver=t.second,distance=t.first;

if(st[ver])continue;

st[ver]=true;

cnt++;

res+=distance;

for(int i=h[ver];~i;i=ne[i])

{

int j=e[i];

if(!st[j]&&w[i]<dist[j])

{

dist[j]=w[i];

q.push({w[i],j});

}

}

}

if(cnt!=n)return 0x3f3f3f3f;

return res;

}

int main()

{

       cin>>n>>m;

       memset(h,-1,sizeof h);

       memset(dist,0x3f,sizeof dist);

       while(m--)

       {

              int a,b,c;

              cin>>a>>b>>c;

              add(a,b,c);

              add(b,a,c);

       }

       int ans=prim();

       if(ans==0x3f3f3f3f)cout<<"impossible"<<endl;

       else cout<<ans<<endl;

}

!!!很明显,边的数量不能太多,遇到完全图上面两种方式就嗝屁了。

小知识:有很少条边或弧(边的条数|E|远小于|V|²)的图称为稀疏图(sparse graph),反之边的条数|E|接近|V|²,称为稠密图(dense graph),完全图是每两个点之间都有一条对应的边。

!小知识:map有find的函数和count函数,find函数的返回值如果不是map.end(),说明是指向你查询的迭代器,count则是返回你查询内容出现的次数。虽然两者时间似乎差不多,但是相较而言,如果只是为了查找是否出现该元素,应该用find,测试时,count花费时间的时间会不稳定地超过find,之前之所以使用count的返回值是1或0来判断是否出现该元素的原因,是因为在map的count中,元素不会出现第二次,很明显,在multimap中它才更具有自己的价值。

题意:问给一些点,问能不能生成一颗最小生成树,输出价值。

解题思路:建立总边集st,查找边权的map,确认是否是当前查找的两点vis数组,

查询用的点集s。(克鲁斯卡尔算法)

重点思路:分治算法,对于k<=n^1/2的时候,可以采用枚举点集时间k^2的方法给克鲁斯卡尔塞边,对于k>n^1/2的时候考虑直接找边。

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define int long long
#define double long double
#define Int __int128
#define pb push_back
#define N (int)1e5+10
#define MAX_LOG 21
#define ff first
#define ss second
#define M 5005
#define ull unsigned long long
using namespace std;
const double PI=3.1415926535897932385;
const ll LLMAX=9223372036854775807ll;
const ll LLMIN=-9223372036854775808ll;
//const int MAX_INT=0x3f3f3f3f;
const int IIMAX=2147483647;
const int IIMIN=-2147483648;
const int INF=0x3f3f3f3f;
typedef pair<ll,ll> PLL;
ll gcd(ll a,ll b)
{
    if(b) while((a%=b)&&(b%=a));
    return a+b;
}//最大公约数函数
ll spid(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        b>>=1;
        a=a*a%p;
    }
    return ans;
}//快速幂函数
///泡沫在阳光下闪烁,像星辰在寂静得夜空中闪耀

///秋雨
// 并查集(Kruskal用)
ll f[N];
inline ll find(ll x)
{
    if (x==f[x])
        return f[x];
    return f[x] = find(f[x]);
}

struct edge
{
    ll l,r,v;
    bool operator<(const edge &t) const
    {
        return v< t.v;
    }
};
vector<edge>st;        // 总边集
map<PLL, ll> mp; // 用于查找边权
ll s[N];   // 查询点集
bool vis[N]; // 需要处理的点(当询问规模过大时使用)
void solve()
{
    ll n,m,q;cin>>n>>m>>q;
    for (int i=1;i<=m;i++)
    {
        ll u,v,w;cin>>u>>v>>w;
        st.push_back({u, v, w});
        mp[{u,v}]=w;
    }
    sort(st.begin(),st.end()); // 边权从小到大排序
    while (q--)
    {
        for(int i=1;i<=n;i++)vis[i]=0;
        ll k;cin>>k;
        for (int i=1;i<=k; i++)
        {
            cin>>s[i];
            f[s[i]]=s[i];
            vis[s[i]]=1;
        }
        vector<edge>vec; // 本轮建立MST所用到的边集
        if(k<1000)///点少取点,点多取边
        {
            for (int l=1;l<=k;l++)
            {
                for (int r=1;r<=k;r++)
                {
                    if(mp.find({s[l], s[r]})!=mp.end())
                    // if (mp[{s[l], s[r]}] != 0)// 直接比较会超时,需要用find
                    {
                        vec.push_back({s[l],s[r],mp[{s[l],s[r]}]});
                    }
                }
            }
            sort(vec.begin(), vec.end());
        }
        else
        {
            vec=st;
        }
        // Kruskal模版
        ll ans=0,cnt=0;
        ll len=vec.size();
        for(int i=0;i<len;i++)
        {    
            ll a=vec[i].l,b=vec[i].r,w=vec[i].v;
            if(vis[a]&&vis[b])
            {
                a=find(a),b=find(b);
                if(a!=b) // 如果两个连通块不连通,则将这两个连通块合并
                {
                    f[a]=b;
                    ans+=w;
                    cnt++;
                }
            }
        }
        if(cnt<k-1)
            ans=-1;
        cout<<ans<<'\n';
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t=1;
//    cin>>t;
    while(t--)
    {
        solve();
    }
}
///在秋天邂逅, 在春天发芽,在夏天壮大,在秋天萧瑟,在冬天萎缩,

///而后,春天再度归来。



///我觉得代码挺好理解。

A:

题意:Chino有如图所示的A和B两种不同类型的地砖Chino的住所设计成了一个n*m平面网格,她打算将其整个铺满这两种类型的瓷砖。她对瓷砖上的图案特别着迷。当相邻瓷砖上的图案完美对齐时,它们形成连续的曲线。在已经放置第一块瓷砖后,Chino好奇是否可以继续铺砖,使得恰好有K条连续曲线。如果存在这样的排列方式,你的任务是帮助她找到至少一个有效的解决方案。

解题思路:发现什么都不做,肯定有2*(n+m)条线伸向外边,一条线顶天两个端点,所以我们至少会有n+m条优美的曲线。很明显题目就是想让我们造环嘛。

我们可以按照全A排或者全B排,你会发现,就是整齐流畅的曲线了,然后你往里面塞圆,往答案上面去靠。显然,构造单位圆(上A,B下B,A)的方式是最优的(5 curves)首先根据固定的一块,按照相邻相反贪心得到左上角地板的类型和可行解的答案区间。如果有解,根据左上角类型按照 AB 交替的方式填,构造最多的单位圆。就好了。最后再根据圆数砍圆。

砍圆很明显,只会改变圆数,而不会改变向外的曲线数。大体思路就这样。

### 2024 牛客暑期训练营 Sort4 题目解析 #### 题目背景与描述 在2024年的牛客暑期训练营中,Sort4是一道涉及字符串排序和字典序比较的题目。该题目的核心在于通过特定的操作改变给定字符串数组的顺序,并最终使得整个序列达到某种最优状态。 #### 解决方案概述 为了有效解决这个问题,需要理解并应用分治算法以及RMQ(Range Minimum Query)技术来优化查询效率[^3]。具体来说: - **初始化阶段**:读入输入数据并将所有字符串存储在一个列表中。 - **预处理部分**:构建笛卡尔树用于快速查找最小值及其位置;同时记录每个节点的信息以便后续更新操作时能够高效定位目标元素的位置。 - 对于每一次交换请求\( (d_i, p_i) \),判断当前待调整项是否满足条件 \( d_i > p_i \)。如果是,则执行相应的移动动作使新的排列更接近理想解; - 使用自定义比较器对修改后的集合重新排序,确保整体结构仍然保持有序特性不变; - 继续上述过程直至完成全部指令集中的每一条命令为止。 ```cpp #include <bits/stdc++.h> using namespace std; struct Node { int val; int idx; }; bool cmp(Node a, Node b){ return a.val < b.val || (a.val == b.val && a.idx < b.idx); } vector<Node> nodes; int n, q; void buildCartesianTree() {/* ... */ } // 执行单步转换操作 void performOperation(int di, int pi) { if(di > pi){ // 当di大于pi时才会引起字典序变化 swap(nodes[di],nodes[pi]); sort(nodes.begin(), nodes.end(),cmp); } } ``` 此段代码展示了如何基于给定条件实施一次有效的变换,并维持全局秩序不受影响。需要注意的是,在实际竞赛环境中可能还需要额外考虑边界情况以及其他潜在陷阱。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值