CUSTACM Summer Camp 2022 Training7(10题)

CUSTACM Summer Camp 2022 Training 7

A. Platforms Jumping

题意

要从0到n+1这个点,中途有m块踏板,可以改变踏板的位置但不能改变相对位置,主人公可以移动的距离是[1,d],问如何到达,或者不能到达

数据范围: 1 ≤ n , m , d ≤ 1000 1 \leq n,m,d \leq 1000 1n,m,d1000

tags:模拟,思维

思路

先把所有的木板都一起放在右边,然后一块一块移到左边直到可以跳到右边来

  1. disp[maxn]维护每个木板可以提供的最远距离(前缀和)
  2. sum求出所有木板的长度,并把所有木板都放在右边
  3. need维护需要跨越的距离,如果d不够,就从左边那一块木板来并更新need

代码

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+5;
int n,m,d;
int c[maxn],dis[maxn],out[maxn];
int sum;

void solve(){
    if(dis[m]+d<n+1){//如果dis[m]+d<n+1,说明所有木板可以提供的最远距离不够,cout<<"NO";
        cout<<"NO"<<endl;
        return;
    }
    else cout<<"YES"<<endl;
    int need=n-sum;
    int x,y;
    for(int i=1;i<=m;i++){
        if(dis[i]<need+c[i]){
            need+=c[i];
            while(c[i]--)out[dis[i]--]=i;
        }
        else if(dis[i]>need+c[i]){
            // while(dis[i]>need+c[i])dis[i]--;
            dis[i]=need+c[i];
            x=i+1,y=need+c[i]+1;
            while(c[i]--)out[dis[i]--]=i;
            break;
        }
        else{
            x=i+1,y=need+c[i]+1;
            while(c[i]--)out[dis[i]--]=i;
            break;
        }
    }
    // cout<<x<<' '<<y<<endl;
    for(int i=x;i<=m;i++){
        while(c[i]--)out[y++]=i;
    }
    for(int i=1;i<=n;i++)cout<<out[i]<<' ';
    cout<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m>>d;
    for(int i=1;i<=m;i++){
        cin>>c[i];
        dis[i]=dis[i-1]+d+c[i]-1;
        // cout<<dis[i]<<' ';
        sum+=c[i];
    }
    solve();
}

复杂度 O ( n ∗ m ) O(n*m) O(nm)


B. k-LCM (hard version)

题意

长度为 k 的数组,整个数组的和为 n ,整个数组的最小公倍数不超过 n / 2,让你构造这样一个数组
简单版本k=3,困难版本 3 <= k <= n。

数据范围: 3 ≤ n ≤ 1 0 9 , 3 ≤ k ≤ 1 0 5 3 \leq n \leq 10^9,3 \leq k \leq 10^5 3n109,3k105

tags:构造,思维

思路

从最小公倍数不大于n/2出发,让一组数的最小公倍数指向我们预期的值n/2(或n/2-1)

对于简单版本,k=3时

  1. 若n为奇数,那么可以构造成n/2,n/2,1
  2. 若n为偶数
    1. 若n/2为奇数,那么可以构造成n/2-1,n/2-1,2
    2. 若n/2为偶数,那么可以构造成n/2,n/4,n/4

对于困难版本,无非就是增加了数组的长度,但是1对最小公倍数没用影响,于是取k-3个1,把k降到3然后用上面的思路

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        int n,k;
        cin>>n>>k;
        if(k>3){
        for(int i=0;i<k-3;i++)cout<<1<<' ';
        n-=k-3;
        k=3;
        }
        if(n&1){
            cout<<n/2<<' '<<n/2<<' '<<1<<' ';
        }
        else {
            if((n/2)&1){
                cout<<n/2-1<<' '<<n/2-1<<' '<<2<<' ';
            }
            else cout<<n/4<<' '<<n/4<<' '<<n/2<<' ';
        }
        cout<<endl;
    }
}

复杂度 O ( k ) O(k) O(k)


C. You Are Given a Decimal String…多源最短路径好题

题意

给定的字符串是机器打印出来的子序列,而这个子序列是计算器上每次计算后的个位数,求0~9中第i行第j列表示i-j计算器,只能加i或者加j,求出现这个子序列最少需要插入多少个数?(或者理解为最少要用多少次计算器)(以矩阵形式输出,若无发成功则输出-1)

数据范围: 1 ≤ s ≤ 2 ∗ 1 0 6 1 \leq s \leq 2*10^6 1s2106

tags:思维,Floyd多源最短路径

思路

对于给定子序列 s 1 s 2 s 3 . . . s k s_1s_2s_3...s_k s1s2s3...sk,我们需要做的是把 子问题:添加一些其他数(路径) a 1 , a 2 . . . ,使 s i (起点)到 s i + 1 (终点),要求添加的数最少 子问题:添加一些其他数(路径)a_1,a_2...,使s_i(起点)到s_{i+1}(终点),要求添加的数最少 子问题:添加一些其他数(路径)a1,a2...,使si(起点)到si+1(终点),要求添加的数最少

其中添加数的过程如同最短路径中添加点来“松弛”的效果,要求添加的数最少就是添加的路径(长度为1)最少。

而填加的数(路径)可以预处理出来:0-9分别+i和+j之后%10

比如0到(0+i)%10有一条长度为1的路径

1到(1+i)%10有一条长度为1的路径

9到(9+j)%10有一条长度为1的路径

很巧秒的多源最短路径变形

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;

string s;
int dis[11][11];

int floyd(int x,int y){
    memset(dis,0x3f,sizeof(dis));
    for(int i=0;i<10;i++){
        dis[i][(i+x)%10]=1;
        dis[i][(i+y)%10]=1;
    }
    for(int k=0;k<10;k++){
        for(int i=0;i<10;i++){
            for(int j=0;j<10;j++){
                dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]);
            }
        }
    }
    int ans=0;
    for(int i=0;i<s.length()-1;i++){
        int x=dis[s[i]-'0'][s[i+1]-'0'];
        if(x==inf)return -1;
        ans+=x-1;
    }
    return ans;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>s;
    for(int i=0;i<10;i++){
        for(int j=0;j<10;j++){
            cout<<floyd(i,j)<<' ';
        }
        cout<<endl;
    }
}

D. Crazy Diamond

题意

给你一个n个数,通过交换两数使它变为有序,n为偶数

交换规则:若 2 ∗ ∣ i − j ∣ ≥ n 2*|i-j| \geq n 2ijn,可交换pi,pj,并且不要求交换的次数最少但是总次数不能超过5n次

数据大小: 2 ≤ n ≤ 3 ∗ 1 0 5 2 \leq n \leq 3*10^5 2n3105

tags:思维

思路

因为1与n/2到n的数都可以交换,n与1到n/2的数都可以交换,所以当不能直接交换时,就把它换到1或n的位置在交换

代码

#include<bits/stdc++.h>
using namespace std;

const int maxn=3e5+5;
int n;
int a[maxn];
vector<pair<int,int>>v;

void sw(int x,int y){
    v.push_back(make_pair(x,y));
    swap(a[x],a[y]);
}


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];
    for(int i=n/2+1;i<=n;i++){//先把后半段中<=n/2的部分移到前面去
        while(a[i]!=i&&a[i]<=n/2){
            if(abs(i-a[i])>=n/2)sw(i,a[i]);
            else{
                sw(i,1);
                sw(1,n);
                sw(n,a[n]);
                sw(1,n);
                sw(i,1);
            }
        }
    }
    for(int i=1;i<=n/2;i++){//对1到n/2的数进行排序,把1当作中间节点
        if(a[i]==i)continue;
        while(a[i]!=i){
            sw(i,n);
            sw(n,a[n]);
            sw(i,n);
        }
    }
    for(int i=n/2+1;i<=n;i++){//对于n/2+1到n的数进行排序,把n当作中间节点
        if(a[i]==i)continue;
        while(a[i]!=i){
            sw(i,1);
            sw(1,a[1]);
            sw(i,1);
        }
    }
    // for(int i=1;i<=n;i++)cout<<a[i]<<' ';
    // cout<<endl;
    cout<<v.size()<<endl;
    for(int i=0;i<v.size();i++)cout<<v[i].first<<' '<<v[i].second<<endl;
}

E. Skyscrapers

题意

n*m网格,每个网格上有一个数字,对于第(i,j)个格子,要求你改变第i行第j列的数,使第i行和第j列的数相对大小不变而最大值最小,求出该最大值并以矩阵形式输出。注意每次分析都的独立进行的。

数据范围: 1 ≤ n , m ≤ 1000 , 1 ≤ a i , a j ≤ 1 0 9 1 \leq n,m \leq 1000,1 \leq a_i,a_j \leq 10^9 1n,m1000,1ai,aj109

tags:思维,二分,离散化

思路

对于每一行每一列,都进行排序,去重操作,然后在对(i,j)分析时,用二分找到a[i],a[j]在上述操作后所在的位置x1,x2,其中该位置肯定是取max(x1,x2),而对于最大值,要先求出最大差值max(y1-x1,y2-x2)(y1、y2是行列单独计算的最大值),最终结果是max(x1,x2)+max(y1-x1,y2-x2);

代码

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e3+5,maxm=1e3+5;
int n,m;
int a[maxn][maxm];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<n;i++)
    for(int j=0;j<m;j++)
    cin>>a[i][j];
    vector<int>hh[maxn],ll[maxm];
    for(int i=0;i<n;i++)
    for(int j=0;j<m;j++)
    hh[i].push_back(a[i][j]);
    for(int j=0;j<m;j++)
    for(int i=0;i<n;i++)
    ll[j].push_back(a[i][j]);
    for(int i=0;i<n;i++){//对于每行排序去重
        sort(hh[i].begin(),hh[i].end());
        hh[i].erase(unique(hh[i].begin(),hh[i].end()),hh[i].end());
    }
    for(int j=0;j<m;j++){//对于每列去重
        sort(ll[j].begin(),ll[j].end());
        ll[j].erase(unique(ll[j].begin(),ll[j].end()),ll[j].end());
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            int x1=lower_bound(hh[i].begin(),hh[i].end(),a[i][j])-hh[i].begin()+1;//二分找a[i]位置
            int y1=hh[i].size();
            int x2=lower_bound(ll[j].begin(),ll[j].end(),a[i][j])-ll[j].begin()+1;//二分找a[j]位置
            int y2=ll[j].size();
            cout<<max(x1,x2)+max(y1-x1,y2-x2)<<' ';
        }
        cout<<endl;
    }
}

复杂度 O ( n ∗ m ∗ ( l o g n + l o g m ) ) O(n*m*(logn+logm)) O(nm(logn+logm))


F. Trailing Loves (or L’oeufs?)数论好题

题意

计算n!在b进制下的后缀0个数

数据范围: 1 ≤ n ≤ 1 0 18 , 2 ≤ b ≤ 1 0 12 1 \leq n \leq 10^{18},2 \leq b \leq 10^{12} 1n1018,2b1012

tags:数论

思路

从特殊问题入手:计算n!在10进制下后缀0的个数

首先任意一个数都可以分解成若干个质因数的和

而对于质因数2和5,2*5=10

而在n!分解为质因数之后2x,5y。x>y是一定的,而出现后缀0的个数取决于y的大小

对于计算y的大小,不用取求出n!,而只用计算n/5,n/25,n/75…的个数之和即可知道n!分解为质因数中5y的y的大小

在来讨论一般性问题:计算n!在b进制下后缀0的个数

上述是10进制下后缀0的个数,我们就凑车2*5=10

同理,这次是b进制下后缀0的个数,我们就凑成 x 1 y 1 ∗ x 2 y 2 ∗ x 3 y 3 . . . = b x_1^{y_1}*x_2^{y_2}*x_3^{y_3}...=b x1y1x2y2x3y3...=b,其中xi为b的质因数

我们只需知道把n!分解成 x i y i ′ x_i^{y'_i} xiyi时最小 y i ′ / y i y'_i/y_i yi/yi的即可,它就是后缀0的个数

而n!分解成质因数xi可以同上面一样计算n/x,n/x2,n/x3…的个数之和

另外一种思路

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll n,b;

map<ll,ll>prime(ll n){//把b分解质因数
    map<ll,ll>res;
    for(ll i=2;i*i<=n;i++){
        while(n%i==0){
            ++res[i];
            n/=i;
        }
    }
    if(n!=1)res[n]=1;
    return res;
}

ll solve(ll n,ll f){//计算n!分解为质因数f的个数
    ll count=0;
    while(n){
        count+=n/f;
        n/=f;
    }
    return count;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>b;
    map<ll,ll>ma=prime(b);
    ll mn=0x3f3f3f3f3f3f3f3f;
    for(auto it=ma.begin();it!=ma.end();it++){
        ll x=solve(n,it->first);
        mn=min(mn,x/it->second);
    }
    cout<<mn<<endl;
}
 

G. New Year and the Permutation Concatenation

题意

给你一个数n,对于1到n的数,将它所有的排列全部按照字典序排序在拼接在一起形成一个序列,问有多少个长度为n的子序列(连续)有1到n每一个数。

数据范围: 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106

tags:找规律

思路

打标找规律

在这里插入图片描述

可以猜测结果一定为n!+x,问题就是打标找出x的规律

可以看到x为(上一个答案的结果-1*n)

比如:当n=3时,结果为9

这时计算n=4时,n!=24,x=32=n*(9-1)=32

于是就可以递归的求出结果,注意先用数组保存计算的阶乘值,方便O(1)时间查找

代码

打标代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

int main(){
    ll n;
    for(int k=1;k<=10;k++){
        n=k;
        ll a[10000]={0};
        for(int i=1;i<=n;i++)a[i]=i;
        vector<int>v;
        do{
            for(int i=1;i<=n;i++)v.push_back(a[i]);
        }while(next_permutation(a+1,a+1+n));
        // for(int i=0;i<v.size();i++)cout<<v[i]<<' ';
        // cout<<endl;
        ll res=0;
        for(int i=0;i+n-1<v.size();i++){
            ll ans=0;
            for(int j=i;j<i+n;j++){
                ans+=v[j];
            }
            if(ans==(n*(n+1))/2)res++;
        }
        ll x=1;
        for(int i=1;i<=k;i++)x*=i;
        cout<<"k="<<k<<"****k!:"<<x<<"****答案:"<<res<<"****答案-k!:"<<res-x<<endl;
    }
}
AC代码
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;

const int maxn=1e6+5;
ll a[maxn]={1};

ll solve(ll n){
    if(n==1)return 1;
    else return (a[n]+n*(solve(n-1)-1)%mod)%mod;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    ll n;
    cin>>n;
    for(int i=1;i<=n;i++)a[i]=(i*a[i-1])%mod;
    cout<<solve(n)<<endl;
}

H. Berland Fair

题意

给你n个数围成一个圈,从第一个数开始顺时针遍历,你手上的数为t,若t>=ai你一定会减掉ai,问否则跳过,问最多可以操作几次

数据范围: 1 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ T ≤ 1 0 18 , 1 ≤ a i ≤ 1 0 9 1 \leq n \leq 2*10^5,1 \leq T \leq 10^{18},1 \leq a_i \leq 10^9 1n2105,1T1018,1ai109

思路:思维,暴力

思路

有技巧的暴力

  1. 若ai和为sum,第一次直接将k减到sum以下,然后暴力,超时
  2. 第二次排序在逐轮更新sum并将t减到sum以下,但是理解错了题意,一定要按顺序遍历,不能改变顺序

正确的暴力:就是在上面第2中方法下,不要取排序的就行

  1. 看剩下的t一轮可以减的数的和sum
  2. 然后一次计算多轮结果,res+=t/sum*count;t%=sum;,直到t比最小的数还小不能进行为止

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn=2e5+5;
ll n,t;
ll a[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>t;
    ll res=0,mn=1e9+5;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        mn=min(mn,a[i]);
    }
    while(t>=mn){
        ll sum=0;
        ll left=t;
        ll count=0;
        for(int i=1;i<=n;i++){
            if(left>=a[i]){
                left-=a[i];
                sum+=a[i];
                count++;
            }
        }
        res+=t/sum*count;
        t%=sum;
    }
    cout<<res<<endl;
}

复杂度:进似 O ( n l o g n ) O(nlogn) O(nlogn)


I. Multiplicity动规好题

题意

在这里插入图片描述

i|bi就是bi能够倍i整除

数据范围: 1 ≤ n ≤ 100000 , 1 ≤ a i ≤ 1 0 6 1 \leq n \leq 100000,1 \leq a_i \leq 10^6 1n100000,1ai106

tags:动态规划

思路

  1. DP抽象意义:dp[i][j]:前i个数中,长度为j的合法子序列的个数
  2. 状态转移:对于第i个数,求出它的所有因子,然后从大到小(如果小的先遍历会对后面大的产生影响)遍历所有因子,因子的大小j即为第i个数放的位置(也等同于长度为j的子序列),于是``dp[i][j]=dp[i-1][j-1]+1`,从前i-1个数中长度为j-1的子序列而来
  3. 边界条件:dp[0][0]=1

有点难想到这状态转移,更具因子来的

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn=1e6+5,mod=1e9+7;
int n;
int a[maxn];
ll dp[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    dp[0]=1;
    for(int i=0;i<n;i++){
        vector<int>v;
        for(int j=1;j*j<=a[i];j++){
            if(a[i]%j==0)v.push_back(j);
            if(a[i]%j==0&&j*j!=a[i])v.push_back(a[i]/j);
        }
        sort(v.begin(),v.end(),greater<int>());
        for(int i=0;i<v.size();i++){
            dp[v[i]]=(dp[v[i]]+dp[v[i]-1])%mod;
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++)ans=(ans+dp[i])%mod;
    cout<<ans<<endl;
}

J. Bicolorings动规好题(状态维)

题意

2行n列的网格,图黑白颜色,问有多少种方法可以是最终有k个连通块

数据大小: 1 ≤ n ≤ 1000 , 1 ≤ k ≤ 2 ∗ n 1 \leq n \leq 1000, 1 \leq k \leq 2*n 1n1000,1k2n

tags:动态规划

思路

加一个维度表示状态:若为0表示该列为同色,若为1表示该列不同色

  1. DP抽象意义dp[i][j][k]:前i列中有j个连通块,其中第i列状态为k

  2. 状态转换方程

     dp[i][j][0]=(2*dp[i-1][j][1]+dp[i-1][j-1][0]+dp[i-1][j][0])%mod;
     dp[i][j][1]=(2*dp[i-1][j-1][0]+dp[i-1][j][1]+dp[i-1][j-2][1])%mod
    

    主要是根据第i列和第i-1列的连通块颜色,来改变第前i-1列中所需的连通块数数量以保证加了第i列后连通块数量为j

  3. 边界条件:dp[1][2][1]=2,dp[1][1][0]=1

  4. 结果:dp[n][k][1]+dp[n][k][0],两个状态都要计算

代码

#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;

const int maxn=1e3+1;
int n,k;
ll dp[maxn][2*maxn][2];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>k;
    dp[1][2][1]=2;
    dp[1][1][0]=2;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=k;j++){
            dp[i][j][0]=(2*dp[i-1][j][1]+dp[i-1][j-1][0]+dp[i-1][j][0])%mod;
            dp[i][j][1]=(2*dp[i-1][j-1][0]+dp[i-1][j][1]+dp[i-1][j-2][1])%mod;
        }
    }
    cout<<(dp[n][k][1]+dp[n][k][0])%mod<<endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值