CUSTACM Summer Camp 2022 Training 5(8题)

CUSTACM Summer Camp 2022 Training 5

bc重复,h题规律题没写题解

A. Even Picture

题意

构造图形,要求染色的格子彼此连通,每个格子都有偶数个邻居,其中有且只有n个格子,这n个格子中的每个格子有4个邻居,输出这些格子的位置坐标。

数据范围: 1 ≤ n ≤ 500 1 \leq n \leq 500 1n500

tags: 构造,找规律

思路

从n=1开始模拟,就可以发现构造方法了

在这里插入图片描述

代码

#include<iostream>
using namespace std;

int main(){
   ios::sync_with_stdio(false);
   cin.tie(0);cout.tie(0);
   int n;
   cin>>n;
   cout<<3*n+4<<endl;
   cout<<0<<' '<<0<<endl;
   cout<<1<<' '<<0<<endl;
   int x=0;
   for(int i=1;i<=n;i++){
       cout<<x<<' '<<i<<endl;
       cout<<x+1<<' '<<i<<endl;
       cout<<x+2<<' '<<i<<endl;
       x++;
   }
   cout<<n<<' '<<n+1<<endl;
   cout<<n+1<<' '<<n+1<<endl;
}

复杂度

O ( n ) O(n) O(n)


B. Codeforces Subsequences

题意

构造一个字符串,使构造出的字符串中“codeforces”字串(该字串可以是不连续的)的数量大于等于k,求满足条件的字符串的最小长度

数据范围: 1 ≤ k ≤ 1 0 16 1 \leq k \leq 10^{16} 1k1016

tags:构造,思维

思路

问题就是如何向codeforces中插入x个字符使出现的字串数量m最大

x=1,无论放哪都行

x=2,最优策略是两个字符不同且位置不连续,如ccoodeforces,m=22

x=3,最优策略是三个字符且位置不连续,如ccooddeforces,m=23

……

x=11,最优策略是2个相同字符,9个不同字符,如cccooddeeffoorrcceess,m=29*31

尽可能去实现乘法,按照上述规律x从1开始枚举,直到m大于1016次方就行,大约x到150就可以了

然后用二分找到第一个大于等于k的数对应的字符数量x,然后按照上述构造规则输出即可

代码中有很多细节讲不清,按自己的思路写吧

代码

#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    ll a[1000]={1};
    ll x=2,y=1,n;
    for(ll i=1;i<=500;i++,y++){//枚举
        a[i]=pow(x,y)*pow(x-1,10-y);
        // cout<<x<<' '<<y<<endl;
        if(i%10==0)x++,y=0;
        if(a[i]>=1e16){
            n=i;
            break;
        }
    }
    // for(int i=1;i<=90;i++)cout<<a[i]<<endl;
    ll k;cin>>k;
    int number=lower_bound(a,a+n,k)-a;
    int n1=number/10+1,n2=number%10;
    string s="*codeforces";
    for(int i=1;i<=n2;i++){
        for(int j=1;j<=n1+1;j++)cout<<s[i];
    }
    for(int i=n2+1;i<=10;i++){
        for(int j=1;j<=n1;j++)cout<<s[i];
    }
}

复杂度

很小,几乎为 O ( 1 ) O(1) O(1)了,因为字符串长度最大也为150左右


D. Ehab and Prefix MEXs

题意

首先有个定义, 给定一个数组, MEX(i)表示数组前i项没有出现过的最小自然数.

要求就是给你一个数组a, 让你构建一个数组b, 要求对于b数组而言, MEX(i)的值为a[i]. 如果无法构建这样的数组, 输出-1;

0 ≤ a i ≤ i , 保证 a i ≤ a i + 1 0 \leq a_i \leq i,保证a_i \leq a_{i+1} 0aii,保证aiai+1

数据大小: 0 ≤ b i ≤ 1 0 6 0 \leq b_i \leq 10^6 0bi106

思路

在第i个位置a数组的值为ai,则需要b数组在前i个数中出现0到ai-1(共ai个数)的所有数

  1. 所以当ai>i时,b数组中前i个数放不齐要求的ai个数,一定为-1。但是题目中ai<=i,所以一定是可以构造的

  2. a i ≠ a i − 1 a_i \neq a_{i-1} ai=ai1

    1. 首先要求b中前i个数中有0到ai-1这些数,有数ai-1
    2. 又要求b中前i-1个数中有0到ai-1-1这些数,且没有数ai-1
    3. 所以ai-1必定出现在bi
  3. a i − 1 = = a i a_{i-1}== a_i ai1==ai

    1. 要注意到ai不下降的,且 0 ≤ a i ≤ i 0 \leq a_i \leq i 0aii,所以理想状态应该是0,1,2,3…或1,2,3,4…,但如果出现 a i − 1 = = a i a_{i-1}==a_i ai1==ai那么前n个数中会缺少某一个数x,此时我们需要把缺少的数x放到bi处,防止这个在a中缺少的数对后面计算MEX产生影响(因为如果不把x加入b中,后续某个MEX(i)可能就是x,但它未在a中出现过)

      有点难理解,看图吧:

      在这里插入图片描述

代码

#include<iostream>
using namespace std;

const int maxn=1e5+5;
int n;
int a[maxn],vis[maxn],b[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        vis[a[i]]=1;//记录已经出现过的数
    }
    int cnt=0;
    for(int i=0;i<=n;i++){
        if(!vis[i])b[cnt++]=i;//记录没有出现过的数
    }
    cnt=0;
    a[0]=a[1];
    for(int i=1;i<=n;i++){
        if(a[i]!=a[i-1])cout<<a[i-1]<<' ';
        else cout<<b[cnt++]<<' ';
    }
}

复杂度

O ( n ) O(n) O(n)


E. Game On Leaves

题意

一颗n个节点的数,两个人分别删除叶节点,最后删掉x节点的人获胜。

数据大小: 1 ≤ t ≤ 10 , 1 ≤ n ≤ 1000 1 \leq t \leq 10,1 \leq n \leq 1000 1t10,1n1000

tags:(树上)博弈

思路

  1. 若x就是一个叶节点(度为1),那么先手必胜

  2. 若x不是一个叶节点,那么当x的度变为1时就会确定胜者

    1. 当删到x的度为2时,如在这里插入图片描述

      没有人会主动去删节点1,不然对手就胜了,每个人都会去删其他其他叶子节点直到x度为1,也就是只剩2个节点,所以我们只需判断n-2的奇偶性就行了

注意:度可以为0!

代码

#include <iostream>
#include<cstring>
using namespace std;

const int maxn = 1005;
int n, k;
int d[maxn];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for (cin >> t; t; t--)
    {
        memset(d,0,sizeof(d));
        cin >> n >> k;
        for (int i = 0; i < n-1; i++)
        {
            int x, y;
            cin >> x >> y;
            d[x]++, d[y]++;
        }
        if (d[k]<=1)
            cout << "Ayush" << endl;
        else
            cout << (((n - 2) & 1) ? "Ashish" : "Ayush") << endl;
    }
}

复杂度

O ( n ) O(n) O(n)


F. AND, OR and square sum位运算好题

题意

给你n个数,对于i!=j,a[i]=x,a[j]=y,我们可以进行一种操作:a[i]=x&y,a[j]=x|y,你可以无限的进行这个操作,问a[i]2的和为最大为多少

数据范围: 1 ≤ n ≤ 2 ∗ 1 0 5 , 0 ≤ a i ≤ 2 20 1 \leq n \leq 2*10^5,0 \leq a_i \leq 2^{20} 1n2105,0ai220

tags:位运算,思维

思路

a i 和 a j 某一二进制位 a_i和a_j某一二进制位 aiaj某一二进制位1和11和00和10和0
&运算1000
|运算1110
a i 和 a j 中在该二进制位上 1 的个数 a_i和a_j中在该二进制位上1的个数 aiaj中在该二进制位上1的个数2个11个11个10个1

可以发现上述操作不会改变任意一二进制位上1的个数

所以就尽可能把所有的1都放在一个数的二进制位中,这样可以实现最后平方和最大

代码

#include<iostream>
#define ll long long
using namespace std;

const int maxn=2e5+5;
int n;
int count[32];

void number1(int x){//提取二进制位上的1
    for(int i=0;i<=21;i++){
        if(x&(1<<i))count[i]++;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=0;i<n;i++){
        int x;cin>>x;
        number1(x);
    }
    // for(int i=0;i<=21;i++)cout<<count[i]<<' ';
    // cout<<endl;
    ll sum=0;
    while(1){
        bool flag=false;
        ll x=0;
        for(int i=0;i<=21;i++){
            if(count[i]){
                x+=(1<<i);
                count[i]--;
                flag=true;
            }
        }
        if(!flag)break;
        sum+=x*x;
    }
    cout<<sum<<endl;
}

复杂度

o ( n ) o(n) o(n)


G. Johnny and Contribution

题意

给定n个点,m条边。每个点都有一个权值 t i t_i ti
问你如何遍历图,使得当前节点为u,与u相连的节点集合{v1,v2,v3,v4,v5…}

求出MEX{v1,v2,v3,v4…v5} 为 x 判断 a[u]是否等于x 。

如果存在序列输出,如果不存在输出-1

数据范围: 1 ≤ n ≤ 5 ∗ 1 0 5 , 0 ≤ m ≤ 5 ∗ 1 0 5 , 1 ≤ t i ≤ n 1 \leq n \leq 5*10^5,0 \leq m \leq 5*10^5,1 \leq t_i \leq n 1n5105,0m5105,1tin

tags:思维

思路

当一个点的权值为ti时,要求遍历它时它的已经遍历的邻居有1到ti-1的所有权值,所以答案肯定是从权值小的点开始遍历

而如何判断会不会符合MEX的要求呢,我们只需保证点u的所有邻居**集合v中有1到ti-1的所有数值且没有ti**即可,因为我们从权值小的点开始遍历,所以那些点肯定是已经遍历过的,只要保证”有“那些点就符合条件

代码

#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn=5e5+5;
int n,m;
struct node{
    int num,b;
    bool operator < (node&x){
        return num<x.num;
    }
}a[maxn];
vector<int>edge[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);//无向图
    }
    for(int i=1;i<=n;i++){
        cin>>a[i].num;
        a[i].b=i;
    }
    for(int i=1;i<=n;i++){
        map<int,int>judge;//用map来存已经在集合v中的权值(一开始我用数组存,会超时哦)
        int st=a[i].b,sco=a[i].num;
        for(auto v:edge[st]){
            judge[a[v].num]=true;
        }
        if(judge[sco]){//如果有于u权值相同的点,不符合条件
            cout<<-1<<endl;
            return 0;
        }
        for(int i=1;i<sco;i++){//判断从1到sco-1的值是否都有
            if(!judge[i]){
                cout<<-1;
                return 0;
            }
        }    
    }
    sort(a+1,a+1+n);//最后排序输出即可
    for(int i=1;i<=n;i++)cout<<a[i].b<<' ';
}

复杂度

按理说 O ( n 2 ) O(n^2) O(n2),因为遍历了从1到ti-1,而ti范围最大又是n。但是过了,有点奇怪,可能数据中ti没有那么大❓❓


I. K-Complete Word字符匹配好题

题意

给你一个长度为n字符串, 让你求出最少修改多少次字母,才能使字符串满足周期为k,且字符串为回文串。保证n能被k整除

数据范围: 1 ≤ k , n ≤ 2 ∗ 1 0 5 1 \leq k,n \leq 2*10^5 1k,n2105

tags:思维,字符串匹配

思路

周期为k又要求是回文字符串,可以分析出它在每一个周期中都是相同的回文字符串,我们只需统计每个周期中字符(是每个小周期中回文字符串回文处对应位置的两个字符)出现的次数,取出现次数最多的字符,把其他字符变为它即可

代码有很多细节,按自己的思路实现就好

代码

#include<iostream>
using namespace std;

const int maxn=2e5+5;
int n,k;
string s;
int cnt[maxn];
string str[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        cin>>n>>k>>s;
        s="*"+s;
        int div=n/k;//有n/k个小回文字符串
        int ans=0;
        for(int i=1;i<=(k+1)/2;i++){//小回文字符串长度为k,需要统计的位置有(k+1)/2个(就是回文位置有(k+1)/2对)
            int cnt[300]={0};
            for(int j=1;j<=div;j++){//对个每个周期的字符串统计该回文位置出现的字符次数
                // cout<<(j-1)*k+i<<' '<<j*k-i+1<<"&&&&&"<<endl;
                cnt[s[(j-1)*k+i]]++;
                if((j*k-i+1)!=((j-1)*k+i))cnt[s[j*k-i+1]]++;//(j-1)*k+i是前面的位置,j*k-i+1是后面的位置,两位置为回文对应位置
            }
            int m=0;
            for(int l='a';l<='z';l++)m=max(m,cnt[l]);//字符出现最多的来选来组成回文字符串
            // cout<<div<<' '<<m<<"**"<<endl;
            if((k&1)&&i==(k+1)/2)ans+=div-m;//注意如果为k为奇数,回文字符串中间是只有1个字符的,是用div-m
            else ans+=2*div-m;
        }
        cout<<ans<<endl;
    }
}

复杂度

O ( n ) O(n) O(n)


J. Navigation System最短路径好题

题意

给你一个有向图,和一个既定的序列 a ,我们沿着这个既定的序列走,在每个位置,导航会重新规划路线 (重新规划的路线一定是到终点的最短路),路线可能和原路线重合也可能和原路线不同,如果不同即是重建,问可能的最小和最大重建次数

数据范围: 2 ≤ n , m ≤ 2 ∗ 1 0 5 , 2 ≤ k ≤ n 2 \leq n,m \leq 2*10^5,2 \leq k \leq n 2n,m2105,2kn

tags:最短路径,思维

思路

先求出终点v到每个点的最短路径(因为是有向图,该过程需要反向建图),然后在从u到v的给定路径来判断

  1. 若dis[u]!=dis[v]+1说明v一定不在最短路径上,所以到了v点导航一定会重新规划路径
  2. 若dis[u]==dis[v]+1,说明v在最短路径上,在寻找有没有V点使dis[u]=dis[V],如果有则导航给定最短路径是V到终点,而不是v到终点,这时可能不重建,可能重建(最小值不变,最大值+1)
  3. 上述过程需要正向的图,不然u的对应点v会弄错

我求最短路径是直接用Dijkstra+优先队列

代码

#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;

typedef pair<int, int> P; // first为距离,second为端点
const int maxn = 2e5 + 10, maxm = 2e5 + 10, inf = 1e9 + 1;
int n, m;
struct edge
{
    int to, cost;
};
vector<edge> g[maxm],gg[maxm];
int dis[maxn];
int a[maxn];

void dijkstra(int s)//dijkstra+优先队列优化求多源最短路径
{
    priority_queue<P, vector<P>, greater<P>> q;
    fill(dis, dis + 1 + n, inf);
    dis[s] = 0;
    q.push(P(0, s));
    while (!q.empty())
    {
        P st = q.top();
        q.pop();
        int pos = st.second;
        if (dis[pos] < st.first)
            continue;
        for (int i = 0; i < g[pos].size(); i++)//反向的图
        {
            edge x = g[pos][i];
            if (dis[x.to] > dis[pos] + x.cost)
            {
                dis[x.to] = dis[pos] + x.cost;
                q.push(P(dis[x.to], x.to));
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < m; i++)
    {
        int u, v;
        cin >> u >> v;
        g[v].push_back(edge{u, 1});//反向的图
        gg[u].push_back(edge{v,1});//正向的图
    }
    int k;
    cin >> k;
    for (int i = 1; i <= k; i++)
        cin >> a[i];
    dijkstra(a[k]);
    // for (int i = 1; i <= k; i++)
    //     cout << dis[i] << ' ';
    // cout << endl;
    int mn = 0, mx = 0;
    for (int i = 1; i < k; i++)
    {
        int u = a[i], v = a[i + 1];
        if (dis[u] != dis[v] + 1)
            mn++, mx++;
        else
        {
            for (int j = 0; j < gg[u].size(); j++)//正向的图
            {
                int to = gg[u][j].to;
                if (dis[to] == dis[u] - 1 && to != v)//注意to!=v,要另一个最短路径,且只要出现了就可以break
                {
                    mx++;
                    break;
                }
            }
        }
    }
    cout << mn << ' ' << mx << endl;
}

复杂度

主要在求最短路径 O ( m l o g n ) O(mlogn) O(mlogn)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值