CUSTACM Summer Camp 2022 Training 9(10题)题解

本文介绍了参加编程竞赛时遇到的几种典型问题及其解决方案,包括动态规划、前缀和、树状数组等算法的应用。针对每个问题,详细阐述了解题思路、代码实现和时间复杂度,并给出了具体实例进行解析。同时,文章强调了在构造解法时思维和策略的重要性,如寻找最优解、合理利用数据结构等。
摘要由CSDN通过智能技术生成

CUSTACM Summer Camp 2022 Training 8

A. Yet Another Counting Problem

题意

给定两个数a、b。有q次询问,每次给出l、r,问满足条件的x个数。

x应满足的条件:
l i ≤ x ≤ r i 并且 ( ( x % a ) % b ) ≠ ( ( x % b ) % a ) l_i \leq x \leq r_i并且((x\%a)\%b)\neq((x\%b)\%a) lixri并且((x%a)%b)=((x%b)%a)
数据范围: 1 ≤ a , b ≤ 200 , 1 ≤ q ≤ 500 , 1 ≤ l i ≤ r i ≤ 1 0 18 1 \leq a,b \leq 200,1 \leq q \leq 500,1 \leq l_i \leq r_i \leq 10^{18} 1a,b200,1q500,1liri1018

思路

  • 可以知道当 0 ≤ x ≤ b − 1 0 \leq x\leq b-1 0xb1时,一定不满足上述条件

    1. 当 x < a 时, ( ( x % a ) % b ) = x 并且 ( ( x % b ) % a ) = x 当x<a时,((x\%a)\%b)=x并且((x\%b)\%a)=x x<a时,((x%a)%b)=x并且((x%b)%a)=x
    2. 当 x = a 时, ( ( x % a ) % b ) = 0 并且 ( ( x % b ) % a ) = x % a = 0 当x=a时,((x\%a)\%b)=0并且((x\%b)\%a)=x\%a=0 x=a时,((x%a)%b)=0并且((x%b)%a)=x%a=0
    3. 当 a < x < b 时 , ( ( x % a ) % b ) = x % a 并且 ( ( x % b ) % a ) = x % a 当a<x<b时,((x\%a)\%b)=x\%a并且((x\%b)\%a)=x\%a a<x<b,((x%a)%b)=x%a并且((x%b)%a)=x%a
  • x ≥ b 时, ( ( x % a ) % b ) = x % a , 但是 ( ( x % b ) % a ) = ? x \geq b时,((x\%a)\%b)=x\%a,但是((x\%b)\%a)=? xb时,((x%a)%b)=x%a,但是((x%b)%a)=

    假设 x = i ∗ a + c = j ∗ b + d , 要使 x % a = ( ( x % b ) % a ) 成立,就需要 c = d % a 假设x=i*a+c=j*b+d,要使x\%a=((x\%b)\%a)成立,就需要c=d\%a 假设x=ia+c=jb+d,要使x%a=((x%b)%a)成立,就需要c=d%a

    可以发现x=k*ab的最小公倍数+d,而d的取值范围为[0,b-1]

    对于计算可以用前缀和思想,求出solve®和solve(l-1)的满足条件的个数,答案就为solve®-solve(l-1)

    一开始直接取硬算solve(l到r)有点写,看了题解才意思到前缀和

代码

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

ll a,b,q;

ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}

ll solve(ll x,ll g){
    ll cnt=x/g;
    ll res=x%g+1;//注意+1,因为当x%g==0时的x也时不符合条件的
    return x-cnt*b-min(res,b);
}


int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--){
        cin>>a>>b>>q;
        if(a>b)swap(a,b);
        ll x=a*b/gcd(a,b);
        while(q--){
            ll l,r;
            cin>>l>>r;
            cout<<solve(r,x)-solve(l-1,x)<<endl;
        }
    }
}

B. Nastya and Scoreboard动态规划,思维好题

题意

给你n个数码位(字符串形式:1代表数码管亮、0代表数码管不亮),问恰好点亮k根数码管,可以得到的最大的n位数是多少。(可以有前导零)

数据范围: 1 ≤ n ≤ 2000 , 0 ≤ k ≤ 2000 1 \leq n \leq 2000,0 \leq k \leq 2000 1n2000,0k2000

tags:动态规划,思维

思路

  1. DP抽象意义dp[i][j]表示从最后一位数码位往前构造到第i位数码位时恰好点亮j根数码管能否构造出数字
  2. 状态转移方程
    1. 先判断第i位数码位可以转化成的0-9中的数字
    2. 求出该转化需要点亮cnt个数码管
    3. for(int m=cnt;m<=k;m++)dp[i][m]|=dp[i+1][m-cnt];,其中m是从n到i已点亮的数码管数量,所以m取值可以是cnt到k,第i位数码需要点亮cnt个,那么i+1位数码恰点亮m-cnt个
  3. 遍历顺序:从最后一位数码位开始遍历,即从n到1
  4. 边界条件dp[n+1][0]=1;构造第n+1位数码位时没用数码管

遍历完dp之后,判断dp[1][k]是否为1,若不是,说明当点亮第1位数码时不能只点亮k根数码管来构造出数字,输出-1

否则每一位都可以构造数数字,从最高位、最大的数字开始遍历,相应数码位可以转换成相应数字则输出

代码有详细解释

代码

复杂度: O ( n ∗ k ) O(n*k) O(nk)

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

const int maxn=2e3+5;
int n,k;
string num[10]={"1110111","0010010","1011101","1011011","0111010","1101011","1101111","1010010","1111111","1111011"};
int val[maxn];//0-9的十进制形式
int st[maxn];//输入字符串的十进制形式
bool dp[maxn][maxn];

void change(){
    for(int i=0;i<10;i++){
        for(int j=0;j<7;j++){
            if(num[i][j]-'0')val[i]|=(1<<(7-j-1));
        }
    }
    // for(int i=0;i<10;i++)cout<<val[i]<<' ';cout<<endl;
    for(int i=1;i<=n;i++){
        string in;cin>>in;
        for(int j=0;j<7;j++)
        if(in[j]-'0')st[i]|=(1<<(7-j-1));
    }
    // cout<<st[1]<<endl;
}

void solve(){
    //定义b[i][j]表示从最后一位数码位往前构造到第i位数码位时恰好点亮j根数码管能否构造出数字
    dp[n+1][0]=1;//构造第n+1位数码没有点亮数码管
    for(int i=n;i>=1;i--){
        for(int j=0;j<=9;j++){
            if((st[i]&val[j])==st[i]){//第i位数码可以通过点亮若干根数码管得到数码j
                // cout<<"i="<<i<<" j="<<j;
                int a=st[i]^val[j],cnt=0;
                for(int m=0;m<7;m++)if(a&(1<<m))cnt++;//cnt就是所需点亮的数码管的数量
                // cout<<" cnt="<<cnt<<endl;
                for(int m=cnt;m<=k;m++)dp[i][m]|=dp[i+1][m-cnt];//注意m是从n到i已点亮的数码管数量,所以m取值是cnt到k,第i位数码需要点亮cnt个,那么i+1位数码需要点亮m-cnt个
            }
        }
    }
    if(dp[1][k]==0){//当点亮第1位数码不能只点亮k根数码管来构造处数字时,输出-1
        cout<<"-1\n";
        return;
    }
    for(int i=1;i<=n;i++){//从最高位开始
        for(int j=9;j>=0;j--){//注意从最大的数开始
            if((st[i]&val[j])==st[i]){
                int a=st[i]^val[j],cnt=0;
                for(int m=0;m<7;m++)if(a&(1<<m))cnt++;
                if(dp[i+1][k-cnt]){
                    cout<<j;
                    k-=cnt;
                    break;
                }
            }
        }
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>k;
    change();
    solve();
}

C. Circle of Monsters

题意

给你n个怪围成一圈,每个怪有ai的血并且死亡后对下一个怪造成bi点伤害(若无第i+1个怪不会对第i+2个怪造成),你每次攻击对怪造成1点伤害,问最少多少次攻击可以杀死全部怪

数据范围: 2 ≤ n ≤ 3 ∗ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 12 2 \leq n \leq 3*10^5,1 \leq a_i,b_i \leq 10^{12} 2n3105,1ai,bi1012

tags:思维,前缀和

思路

  • 最终结果取决于第一个杀死的怪:因为杀死一个怪后最优策略是带着其爆炸伤害继续杀后面的怪,若去杀前面的怪那么爆炸伤害就每完全利用好。所以选择好第一个怪那么结果也就确定了
  • 枚举每个怪作为第一个杀死的怪,计算其结果去最小值
  • 计算结果不能在去暴力,不然时间复杂度为 O ( n 2 ) O(n^2) O(n2),利用前缀和预处理
    • sum[i]表示杀死前i个怪需要的攻击次数
    • 注意if(a[1]-b[n]>0)sum[1]=a[1]-b[n];else sum[1]=0;因为在实际计算中i=1会受到i=n的爆炸伤害
    • 若选择第i个怪作为第一个杀死的,那么结果为:ans=a[i]+sum[n]-sum[i]+sum[i-1](本身的血量a[i])+(杀死i+1到n的怪的攻击次数sum[n]-sum[i+1-1])+(杀死1到i-1的怪的攻击次数sum[i-1]-sum[1-1]=sum[i-1])
    • 注意若第一个怪为1或n时需要特判

代码

复杂度: O ( n ) O(n) O(n)

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

const int maxn=3e5+5;
int n;
ll h[maxn],b[maxn];
ll sum[maxn];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        cin>>n;
        for(int i=0;i<=n;i++)sum[i]=0;
        for(int i=1;i<=n;i++)cin>>h[i]>>b[i];
        if(h[1]-b[n]<0)sum[1]=0;
        else sum[1]=h[1]-b[n];
        for(int i=2;i<=n;i++){
            ll x=h[i]-b[i-1];
            if(x<0)sum[i]=sum[i-1];
            else sum[i]=x+sum[i-1];
        }
        ll mn=0x3f3f3f3f3f3f3f3f;//开大点,若只开0x3f3f3f3f会WA
        for(int i=1;i<=n;i++){
            ll ans=h[i];
            if(i>1)ans+=sum[i-1];
            if(i<n)ans+=sum[n]-sum[i];
            mn=min(ans,mn);
        }
        cout<<mn<<endl;
    }
}

D. Carousel构造好题

题意

给你n个数,其中n与1相邻。给n种数染色(用了k种颜色),相邻的不同种数必须颜色不同,问k的最小值以及染色方法(用1到k的数字表示颜色)

数据范围: 3 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ t i ≤ 2 ∗ 1 0 5 3 \leq n \leq 2*10^5,1 \leq t_i \leq 2*10^5 3n2105,1ti2105

tags:思维,构造

思路

  1. 只有一种数那么只用一种颜色即可可以用set来判断是否只有一种数

  2. n为偶数那么只用2种颜色,染色方法是1、2循环染色。因为1、2循环输出一定保证了相邻两数之间不同色

  3. n为奇数

    1. n和n-1是同种数那么只需2种颜色,1到n-1依然1、2循环染色,最后n染成2这样保证了n(染成2)与1(染成1)一定不同色

    2. n和1是同种数那么只需2种颜色:1到n-1依然1、2循环染色,最后n染成1这样保证了n(染成1)与n-1(染成2)一定不同色

    3. t[n]!=t[n-1]&&t[n]!=t[1]:那就要想办法使n的颜色与n-1和1都不同

      1. 如果延续前面的染色方法:那么需要3种颜色,1到n-1循环染色,最后n染成3这样保证了n(染成3)与n-1(染成2)和1(染成1)一定不同色

      2. 但是,比起上面那种方法还有更优解!!!改变1、2的循环染色

        比如先1、2循环染色,中间变为2、1循环染色,最后n-1染色成为1,1染色成为1,那么只需把n染色成2就行了。这样只用了2种颜色

        而为了实现改变循环:就需要要中间出现重复连续的数,在那边改变循环

思路很清晰了,但是细节有很多需要考虑(wa了蛮多次😭),下面代码可能会有点难看懂,按上述思路自己写就行

代码

复杂度: O ( n ) O(n) O(n)

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

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

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        cin>>n;
        set<int>s;
        int x=0,y=0,judge=0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            s.insert(a[i]);
            if(i!=1&&i!=n&&a[i]==a[i-1])x=i-1,y=i,judge=1;
        }
        if(s.size()==1){
            cout<<1<<endl;
            for(int i=0;i<n;i++)cout<<1<<' ';
            cout<<endl;
            continue;
        }
        if((n&1)==0){
            cout<<2<<endl;
            for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
            cout<<endl;
        }
        else{
            if(a[n]==a[n-1]){
                cout<<2<<endl;
                for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
                cout<<2<<endl;
            }
            else if(a[n]==a[1]){
                cout<<2<<endl;
                for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
                cout<<1<<endl;
            }
            else if(judge){
                cout<<2<<endl;
                for(int i=0;i<x/2;i++)cout<<1<<' '<<2<<' ';
                if(x&1){
                    cout<<1<<' '<<1<<' ';
                    for(int i=0;i<(n-y)/2;i++)cout<<2<<' '<<1<<' ';
                    cout<<2<<endl;
                }
                else {
                    for(int i=0;i<(n-x)/2;i++)cout<<2<<' '<<1<<' ';
                    cout<<2<<endl;
                }
            }
            else {
                cout<<3<<endl;
                for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
                cout<<3<<endl;
            }
        }
    }
}

E. Game with Chips

题意

给你一个n*m网格,有k个物品放入(sxi,syi)中,经过一系列移动,要求第i个物品经过(exi,eyi)

  1. 移动:所有物品上下左右UDLR移动
  2. 遇到墙壁原地不动
  3. 最终位置不必在(exi,eyi),经过就行
  4. 一个格子可以装多个物品
  5. 最多可以平移2*n*m次

数据范围: 1 ≤ n , m , k ≤ 200 1\leq n,m,k \leq 200 1n,m,k200

tags:思维,构造

思路

以前做过类似全部物品平移的题,很难去分别控制每个物品经过给定点,所有想法一般都是把所有点集中起来移在一起移动

  • 集中:最终的集中的位置(绝大情况)一定在四个角落,为了方便起见移到左上角
    • 那么就统计最大横纵坐标xx、yy:需要向L移xx-1步,向U移yy-1步
    • 最大可能移动n+m-2次
  • 因为允许的总次数为2*n*m,所以很自然的发现接下来从左上角绕S型遍历全地图就行
    • 注意向D移动了n-1次
    • 每一层会移动(LR交替)m-1次,n层的话就是n*m-n次
    • 总移动n*m-1次
  • 最大可能移动n*m+n+m-3次,一定会在2*n*m之内

代码

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

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

const int maxn=2005;
int n,m,k;

int main(){
    cin>>n>>m>>k;
    int xx=0,yy=0;
    for(int i=0;i<k;i++){
        int x,y;cin>>x>>y;
        xx=max(xx,x-1);
        yy=max(yy,y-1);
    }
    for(int i=0;i<k;i++){
        int x,y;
        cin>>x>>y;
    }
    cout<<n*m-1+xx+yy<<endl;
    for(int i=0;i<xx;i++)cout<<'U';
    for(int i=0;i<yy;i++)cout<<'L';
    for(int i=1;i<=n;i++){
        if(i&1)for(int j=1;j<m;j++)cout<<'R';
        else for(int j=1;j<m;j++)cout<<'L';
        if(i!=n)cout<<'D';
    }
}

F. Primitive Primes数论(多项式)好题

题意

给一个n个项的多项式,系数都是整数,且未知数项为[0,n-1]次,保证所有的n个系数的gcd为1。再同理给出另一个m个项的多项式。再给一个质数p,求他们两个的多项式乘积的系数中,第几次项的系数不是p(p是质数)的倍数,题目保证有解,写出其中任意一个解。

数据范围: 1 ≤ n , m ≤ 1 0 6 , 2 ≤ p ≤ 1 0 9 , 1 ≤ a i , b i ≤ 1 0 9 1 \leq n,m \leq 10^6,2 \leq p \leq 10^9,1 \leq a_i,b_i \leq 10^9 1n,m106,2p109,1ai,bi109

tags:数论(本多项式)

思路

image-20220802010046994
本多项式及高斯引理

本多项式及高斯引理

由上述可知,只用找到第一个不能倍p整除的aibj,结果为i+j

代码

复杂度: O ( n ) O(n) O(n)

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

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,m,p;
    cin>>n>>m>>p;
    int x=-1,y=-1;
    for(int i=0;i<n;i++){
        int in;cin>>in;
        if(in%p!=0&&x==-1)x=i;
    }
    for(int i=0;i<m;i++){
        int in;cin>>in;
        if(in%p!=0&&y==-1)y=i;
    }
    cout<<x+y<<endl;
}

G. Moving Points树状数组好题

题意

在OX坐标轴上有n个点,每个点的坐标和速度分别为xi,vi,在任意时间之后,第i、j两点的距离最小值为d(i,j),问 ∑ 1 ≤ i , j ≤ n d ( i , j ) \sum \limits_{1 \leq i,j \leq n}d(i,j) 1i,jnd(i,j)

数据范围: 2 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ x i ≤ 1 0 8 , − 1 0 8 ≤ v i ≤ 1 0 8 2 \leq n \leq 2*10^5,1 \leq x_i \leq 10^8,-10^8 \leq v_i \leq 10^8 2n2105,1xi108,108vi108

tags:思维,树状数组

思路

树状数组-现学现用

对于x[i]<x[j]并且v[i]<=v[j]的情况,那么d(i,j)=x[j]-x[i],否则d(i,j)=0,于是就用树状数组维护坐标<x[i]并且速度<=x[i]的点(我感觉这个树状树状维护的有点玄学,想不到)

  • 若知道有cnt个点(x[1],x[2]…x[cnt])满足坐标<x[i]并且速度<=x[i],于是有:

    ∑ 1 ≤ j ≤ n d ( i , j ) = ∑ 1 ≤ j ≤ c n t d ( i , j ) = ( x [ i ] − x [ 1 ] ) + ( x [ i ] − x [ 2 ] ) + . . . + ( x [ i ] − x [ c n t ] ) = c n t ∗ x [ i ] − ∑ i = 1 c n t x [ i ] = c n t ∗ x [ i ] − s u m \sum \limits_{1 \leq j \leq n}d(i,j)=\sum \limits_{1 \leq j \leq cnt}d(i,j)=(x[i]-x[1])+(x[i]-x[2])+...+(x[i]-x[cnt])=cnt*x[i]-\sum \limits_{i=1}^{cnt}x[i]=cnt*x[i]-sum 1jnd(i,j)=1jcntd(i,j)=(x[i]x[1])+(x[i]x[2])+...+(x[i]x[cnt])=cntx[i]i=1cntx[i]=cntx[i]sum

    所以我们需要用树状数组维护的东西有cnt(满足上述条件的点的个数),sum(满足…点的坐标和)

    需要的功能能为单点修改,区间插叙

  • 速度树状数组bitv[]

    1. 首先需要对所有点按坐标从小到大排序*(这样就保证了坐标<x[i],下面就可以只对速度进行讨论了)*
    2. 对速度数组进行离散化排序+去重),离散化后的速度数组大小就为树状数组大小
    3. 遍历所有的点,用二分法找出该点速度在树状树组中的位置: int index=lower_bound(v+1,v+1+len,a[i].second)-v;
    4. (*区间1到index)查询(坐标<它并且速度<=它)*的点的个数: ll cnt=query(index,bitv);
    5. *(单点index)*修改速度为a[i].second的个数: update(index,1,bitv);
  • 坐标树状数组bita[]

    1. 在上述步骤二得到index之后
    2. (区间1到index)查询(满足…点的坐标和) ll sum=query(index,bita);
    3. *(单点index)*修改速度为a[i].second的点的坐标和:update(index,a[i].first,bita);

第一次写树状数组的题目,有点难

代码

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

typedef pair<int,int>P;
const int maxn=2e5+5;
int n;
P a[maxn];
int v[maxn];
int len;//树状数组大小
ll bitv[maxn],bita[maxn];

int lowbit(int x){
    return (x&(-x));
}

void update(int x,int val,ll*bit){
    for(int i=x;i<=len;i+=lowbit(i)){
        bit[i]+=val;
    }
}

ll query(int x,ll*bit){
    ll ans=0;
    for(int i=x;i>=1;i-=lowbit(i)){
        ans+=bit[i];
    }
    return ans;
}

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].first;
    for(int i=1;i<=n;i++){
        cin>>a[i].second;
        v[i]=a[i].second;
    }
    sort(a+1,a+1+n);
    sort(v+1,v+1+n);
    len=unique(v+1,v+1+n)-v-1;
    // cout<<len<<endl;
    ll ans=0;
    for(int i=1;i<=n;i++){
        int index=lower_bound(v+1,v+1+len,a[i].second)-v;
        ll cnt=query(index,bitv);
        update(index,1,bitv);
        ll sum=query(index,bita);
        update(index,a[i].first,bita);
        ans+=cnt*a[i].first-sum;
    }
    cout<<ans<<endl;
}

H. Perfect Keyboard思维好题

题意

给你一个字符串s,现在要求你构造一个26个小写字母的一个排列t,使得给出的字符串s中,相邻字母在你构造的排列t中也是相邻的 有就输出YES 并输出构造的排列 没有就输出NO。

数据范围: 1 ≤ t ≤ 1000 , 1 ≤ ∣ s ∣ ≤ 200 1\leq t \leq 1000,1 \leq |s| \leq 200 1t1000,1s200

tags:思维,图论

思路

在字符串s中,字符相邻说明这两字符之间有一条无向边,只要建立一个邻接矩阵就行*(注意可能多次出现相同的边,因此邻饥接矩阵比较方便)*

  1. 统计每个点的度,当有点的度>2时一定不能成功
  2. 注意样例三,当出现环时也不能成功:步骤1会排除一些环,剩下只需判断是否有“一个整个大环(首尾相连的环)”,只需判断有无度为1的点就行

判断好有解后,从度为1的点开始遍历所有边并统计字符

题解

复杂度: O ( t ∗ n ) O(t*n) O(tn)

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

string s;
int a[30][30];
int vis[30];
vector<char>out;

void dfs(int x){
    if(vis[x])return;
    vis[x]=true;
    out.push_back(x+'a');
    for(int j=0;j<26;j++)
    if(a[x][j])dfs(j);
}

void solve(){
    out.clear();
    memset(vis,0,sizeof(vis));
    memset(a,0,sizeof(a));
    map<int,int>m;
    for(int i=0;i<s.length()-1;i++){
        a[s[i]-'a'][s[i+1]-'a']=1;
        a[s[i+1]-'a'][s[i]-'a']=1;//无向边
    }
    for(int i=0;i<26;i++){
        for(int j=0;j<26;j++){
            if(a[i][j])m[i]++;//统计度
        }
    }
    for(int i=0;i<26;i++)if(m[i]>2){//如果有度>2不可能成功
        cout<<"NO"<<endl;
        return;
    }
    for(int i=0;i<26;i++)if(m[i]<2)dfs(i);//这里相当于判断了是否有度为1的点,若没有那么就会出现一个大环(度全为2),就会出现一些字符没用统计,在下面if判断下就行
    if(out.size()<26)cout<<"NO"<<endl;
    else{
        cout<<"YES"<<endl;
        for(int i=0;i<out.size();i++)cout<<out[i];
        cout<<endl;
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        cin>>s;
        solve();
    }
}

I. Ayoub’s function

题意

给你长度为n的由01组成的字符串,其中由m个1,问含有1的字串的总数的最小值

数据范围: 1 ≤ t ≤ 1 0 5 , 1 ≤ n ≤ 1 0 9 , 0 ≤ m ≤ n 1 \leq t \leq 10^5,1 \leq n \leq 10^9,0 \leq m \leq n 1t105,1n109,0mn

tags:组合数学,思维

思路

首先数据范围很大(t组输入也包含在内),肯定是可以在O(1)时间的结果

若m=0,那么总数一定为sum=(n+1)*n/2

当m=n-1时,会产生1个不含1的子串*(就是它本身)*

当m=n-2时,如何放这两个0很关键:

  • 若2个0连着,那么会产生(2+1)*2/2个不含1的子串
  • 但是若两个0之间有1隔着,那么只会产生2个不含1的子串

于是可以发现:有1分隔的0不会相互影响,而连续的0(假设有x个)会产生(x+1)*x/2个不含1的字串

于是就要让m个1尽可能的去分隔n-m个0

代码

复杂度: O ( t ) O(t) O(t)

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

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        ll n,m;
        cin>>n>>m;
        ll x=(n-m)/(m+1);
        ll y=(n-m)%(m+1);
        ll ans=(n+1)*n/2;
        ans-=(x+1)*x/2*(m+1-y)+(x+2)*(x+1)/2*y;
        cout<<ans<<endl;
    }
}

J. Infinite Prefixes

题意

给你一个长度为n的由01组成的字符串s,令字符串t=ssswss…,也就是说t无限长

问题字符串前缀的balance=num(0)-num(1)=x的数量,若有无限个输出-1

数据范围: 1 ≤ n ≤ 1 0 5 , − 1 0 9 ≤ x ≤ 1 0 9 1 \leq n \leq 10^5,-10^9 \leq x \leq 10^9 1n105,109x109

tags:思维

思路

看样例010010:

  1. 首先分析出前n个前缀字符串的balance

    1 0 1 2 1 2

  2. 继续进行t=ss的分析

    3 2 3 4 3 4 ⇨⇨⇨ 3=1+2,2=0+2,3=1+2,4=2+2,3=1+2,4=2+2:前n个balance+第一轮末尾balance2

  3. 继续进行t=sss的分析

    5 4 5 6 5 6 ⇨⇨⇨ 5=1+4,4=0+4,5=1+4,6=2+4,5=1+4,6=2+4:前n个balance+第二轮末尾balance4

可以发现**前n个balance和第n个balance(令其为b[n])是关键,后面几轮的迭代都是前n个balance+(轮数-1)***b[n]

知道了大致方向就来讨论了,令ans为最终结果

  • **注意:若x为0,那么空字符串也符合条件哦!!!**此时初始ans=1
  • 判断前n个balance是否有等于x,更新ans
  • 特别判断:若第n个balance为0,那么后面几轮的balance都是第一轮的balance
    • 若ans!=0,说明前n个balance中有x,那么后面无限轮中都会有符合条件的,那么应输出-1
    • 若ans=0,那么后面无限轮中都没用符合条件的,那么应该输出0
  • 判断若干轮是否会出现x,只需对前n个balance(b[i])进行循环
    • 若能,那么说明b[i]+(轮数-1)*b[n]=x,也就是说 ( x − b [ i ] ) % b [ n ] = = 0 (x-b[i])\%b[n]==0 (xb[i])%b[n]==0
    • 否则不能
    • 注意:还需要x!=b[i]的条件,因为x==b[i]是在前面也就是第一轮已经计算完了

代码

复杂度: O ( n ) O(n) O(n)

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

int n,x;
string s;

void solve(){
    vector<int>v;
    long long ans=0,cnt=0;
    for(int i=0;i<n;i++){
        if(s[i]=='0')cnt++;
        else cnt--;
        v.push_back(cnt);
        if(cnt==x)ans++;
    }
    if(x==0)ans++;
    if(v[n-1]==0){
        if(ans!=0)cout<<-1<<endl;
        else cout<<ans<<endl;
        return;
    }
    for(int i=0;i<n;i++){
        int temp=x;
        temp-=v[i];
        if((temp>0&&v[n-1]<0)||(temp<0)&&v[n-1]>0)continue;
        if(temp%v[n-1]==0&&x!=v[i])ans++;
    }
    cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int t;
    for(cin>>t;t;t--){
        cin>>n>>x>>s;
        solve();
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很高兴为您提供 Mathor Cup 2022 D 的解思路。 目描述: 给定一个 $n\times n$ 的矩阵 $A$,其中 $A_{i,j}\in\{0,1\}$。你可以进行任意次以下操作: 1. 将第 $i$ 行取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$); 2. 将第 $j$ 列取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$)。 请你计算通过若干次操作后,能够使得矩阵 $A$ 的每一行和每一列的 $1$ 的个数相等的最小操作次数。 解思路: 本可以使用贪心和二分图匹配的思想来解决。具体步骤如下: 1. 统计每一行和每一列的 $1$ 的个数,设 $row_i$ 表示第 $i$ 行的 $1$ 的个数,$col_j$ 表示第 $j$ 列的 $1$ 的个数。 2. 如果每一行和每一列的 $1$ 的个数都相等,那么无需进行任何操作,直接输出 $0$。 3. 如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将该行取反,将 $row_i$ 减一,将 $col_j$ 加一。 4. 如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将该列取反,将 $col_j$ 减一,将 $row_i$ 加一。 5. 重复步骤 3 和步骤 4,直到每一行和每一列的 $1$ 的个数都相等。 6. 计算进行的操作次数,输出结果。 需要注意的是,为了避免重复计算,我们可以使用二分图匹配的思想来进行操作。将每一行和每一列看做二分图的两个部分,如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将第 $i$ 行和所有 $1$ 的个数比该行少的列建立一条边;如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将第 $j$ 列和所有 $1$ 的个数比该列少的行建立一条边。最后,将二分图的最小路径覆盖数乘以 $2$ 就是最小操作次数。 时间复杂度:$O(n^3)$。 完整代码:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值