Codeforces Round #744 (Div. 3)

15 篇文章 0 订阅
3 篇文章 0 订阅
本文介绍了四道编程竞赛题目,涉及字符串处理、序列操作、地图模拟和谈话次数优化。AC代码分别使用C++实现,展示了如何在限定操作次数内使序列有序、构建特定形状、最大化谈话次数以及优化序列字典序。文章通过贪心策略和优先队列等算法思想,解析了解题思路与关键实现细节。
摘要由CSDN通过智能技术生成

Codeforces Round #744 (Div. 3)

A

AC代码
#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;

void solve()
{    
    int a,b,c;
    a = b = c = 0;
    string str;
    cin>>str;
    for(int i=0 ;i<str.size();i++){
        if(str[i]=='A')a++;
        else if(str[i]=='B')b++;
        else c++;
    }
    if(a + c == b) cout << "YES\n";
    else cout << "NO\n";
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

B

题目

给一个序列 , 不断用一个偏移操作,最终使之有序,模拟这个过程,操作最大次数限制为序列长度。
操作:每次选取一段区间,向左偏移一个偏移量d。

思路

  • 我们每次把一个数字放到它该在的地方,如此重复n次一定可以得到答案,且总数不会超过n。
  • 因此我们只要每次找到第一小的放到第一个,第二小的放到第二个,第三小的放到第三个···以此类推。
  • 需要说明的是,每次放好后就不用操作,又因为排好的段连续,所以后续的操作不会影响之前排好的。

AC代码

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e5 ,mod=1e9 + 7;
int n;
int b[N];
int a[N];

void get_mn(int st,int &id,int &mn)
{
    for(int i = st;i<n;i++){
        if(mn > a[i]) mn = a[i], id = i;
    }
}
struct Ans{
    int l,r,d;
}ans[N];
void solve()
{    
    cin >> n;
    for(int i =0  ;i< n ;i++) cin>> a[i];
    int id = 0, mn = inf;
    int cnt = 0;
    for(int i = 0;i < n ;i++){
        mn = inf;
        get_mn(i,id,mn);
        int l = i , r = id ;
        int  d = r - l;
        if(!d)continue;
        int tmp = a[id];
        for(int j = r-1 ; j>= l; j--){
            a[j+1] = a[j];
        }
        a[i] = tmp;
        ans[cnt++] = {l+1,r+1,d};
    }
    cout << cnt << endl;
    for(int i = 0;i< cnt ;i++){
        cout <<ans[i].l << ' '<<ans[i].r<<" "<<ans[i].d<<endl;
    }
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

C

题目

给一个地图,看判断是否可以由最小长度为k的“tick”构成,
tick:一个左右对称的对勾,它的长度为他的臂长

思路

  • 对每一个被填充部分,将其作为对勾的中心,向左上右上搜索,进行可行性判断即可,代码实现有一些细节。

AC代码

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 1e2 ,mod=1e9 + 7;
int n , m,k;
char mm[N][N];
bool vis[N][N];
int d[2][2] ={{1,1},{-1,1}};

int dfs_l(int x,int y,int op)
{
    int nx = x - 1,ny = y - 1;
    int res = 0;
    if(op>0)vis[nx][ny] = 1;
    if(nx<0||ny<0||nx>=n||ny>=m||mm[nx][ny]!='*') return 0;
    return dfs_l(nx,ny,op-1) + 1;
}
int dfs_r(int x,int y,int op)
{
    int nx = x - 1,ny = y +1;
    int res = 0;
    if(op>0)vis[nx][ny] = 1;
    if(nx<0||ny<0||nx>=n||ny>=m||mm[nx][ny]!='*') return 0;
    return dfs_r(nx,ny,op-1) + 1;
}

void solve()
{    
    memset(vis,0,sizeof vis);
    cin >> n>>m >>k;
    for(int i = 0;i<n ;i++)
        for(int j = 0; j<m; j++)
            cin>> mm[i][j];
    int cnt = 0;
    for(int i =0;i<n;i++)
        for(int j = 0;j<m;j++){
            if(mm[i][j] == '*'){
                cnt ++;
                int l = dfs_l(i,j,0), r = dfs_r(i,j,0);
                // cout << l<<' '<<r<<endl;
                if(min(l,r) >= k){
                    vis[i][j] = 1;
                    int mn = min(l,r);
                    dfs_l(i,j,mn),dfs_r(i,j,mn);
                }
            }
        }
    for(int i =0 ;i<n ;i++)
        for(int j =0;j<m;j++)
            cnt -=vis[i][j];
    if(!cnt) cout<<"YES\n";
    else cout<<"NO\n";
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}
/* 
1
5 5 1
.....
*...*
.*.*.
..*.*
...*.


 */

D

题目

一些人谈话,每个人有一个谈话次数限制,每谈一次话,俩人都减少谈话次数,问最大谈话次数是什么

思路

  • 我们贪心的考虑这个问题,为了尽可能不浪费,也就是不让人落单,直觉上认为谈话次数最多的人,最有可能被剩下
  • 因此我们每次让次数最多的人参与谈话即可,这可以用优先队列很简单的实现。

AC代码

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
struct Ans{
    int x,y;
} ans[N];
priority_queue<PII>q;
void solve()
{    
    ll tot = 0;
    while(q.size())q.pop();
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        int t;
        scanf("%d",&t);
        if(t) q.push({t,i});
    }
    int len = 0;
    while(q.size()>1){
        PII u = q.top();
        q.pop();
        PII v = q.top();
        q.pop();
        tot ++;
        ans[len++] = {u.second,v.second};
        u.first --;
        v.first --;
        if(u.first > 0){
            q.push(u);
        }
        if(v.first > 0){
            q.push(v);
        }
    }
    printf("%lld\n",tot);
    for(int i = 0;i<len;i++){
        printf("%d %d\n",ans[i].x,ans[i].y);
    }
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

E1

题目

用deque操作一个序列,求字典序最小的那个序列。

思路

  • 贪心,只需要比较队首元素和要加入的元素的大小即可,直接拿deque模拟。

AC代码

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
int a[N];
deque<int>q;
void solve()
{    
    while(q.size())q.pop_back();
    scanf("%d",&n);
    for(int i = 0;i<n;i++)scanf("%d",a+i);
    for(int i = 0;i<n;i++){
        if(q.empty())q.push_back(a[i]);
        else{
            int t =q.front();
            if(a[i]<=t)q.push_front(a[i]);
            else q.push_back(a[i]);
        }
    }
    for(auto it:q){
        printf("%d ",it);
    }
    puts("");
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

E2

题目

给一个序列,用deque操作这个序列,求所有可得到的序列中,逆序数的最小值。

思路

  • 考虑一次操作,插入一个元素 a i a_i ai,只有插入队首或队尾,插入队尾时, [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai1]中比 a i a_i ai大的元素提供答案的增量,插入队首时, [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai1]中小于和等于 a i a_i ai的元素个数提供答案的增量。
  • 对于每一次操作,可以发现和元素的顺序无关,因此前状态的选择不会对后过程产生任何影响,因此每次的最优局部解就可以得到全局最优解,所以每次贪心最小答案增量即可。
  • 可以发现答案的增量要么是 [ a 1 , a i − 1 ] [a_1,a_{i-1}] [a1,ai1] a i a_i ai的逆序数,要么是正序数(即比 a i a_i ai大的元素个数或者小于等于 a i a_i ai的元素个数)
  • 那么应该用什么方法实现快速的求出这个序列一段前缀的逆序数呢?树状数组可以做到。
树状数组求逆序

关于这个问题,首先明确什么是树状数组,网上有很多优秀博客可以参考,这里不过多赘述。
然后对于用它来求逆序数,其思维过程如下:

  • 我们首先朴素的求逆序,可以采用维护一个 c n t cnt cnt数组,记录排行为i的数字出现的次数。
  • 显然当我们遍历到序列上的某个数之后,我们向前搜索所有比它小的数字的个数,再减去总数就是逆序数了,这显然可以看成不断对 c n t cnt cnt数组求前缀和单点更新的操作,这就可以用树状数组来维护了。
  • 所以每次遍历到一个 a i a_i ai,只要通过 l o w b i t lowbit lowbit求出前缀和,就可以得到它的正序数,进而得到逆序数,之后把这个数插入树状数组,它所提供的增量为1(即 a i a_i ai的出现次数增加一)

更多细节见注释

AC代码

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define debug(x) cout<<"> "<< x<<endl;
#define ull unsigned long long
#define endl '\n'
#define lowbit(x) x&-x
//#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N =10 + 2e5 ,mod=1e9 + 7;
int n;
int a[N],b[N],tr[N];
void add(int p ,int v)// 更新操作
{
    for(int i = p;i<= n ;i+= lowbit(i)) tr[i] +=v;
}
int getsum(int p ) // 得到前缀和
{
    int res = 0;
    for(int i = p;i>0;i-= lowbit(i)) res += tr[i];
    return res;
}
void solve()
{    
    memset(tr,0,sizeof tr);
    scanf("%d",&n);
    for(int i =1 ;i<= n ;i++) scanf("%d",a+i), b[i] = a[i];
    sort(b+1,b+1+n);// 因为只关心相对大小,进行一个离散化
    for(int i = 1;i<= n ;i++) a[i] = lower_bound(b+1,b+1+n,a[i]) - b;
    ll res = 0;
    for(int i = 1;i<= n ;i++){
        res += min(getsum(a[i]-1),i-1 - getsum(a[i]));// 正序,所有比它小的 逆序,总数 - 所有小于等于它的
        add(a[i],1);// update
    }
    printf("%lld\n",res);
}
signed main()
{
    ios::sync_with_stdio();cin.tie();cout.tie();

    int T;cin>>T;
    while(T--)
        solve();

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值