2022-2-26

卡片

题目描述

小蓝有很多数字卡片,每张卡片上都是数字 0 到 9。 小蓝准备用这些卡片来拼一些数,他想从 1 开始拼出正整数,每拼一个, 就保存起来,卡片就不能用来拼其它数了。 小蓝想知道自己能从 1 拼到多少。 例如,当小蓝有 30 张卡片,其中 0 到 9 各 3 张,则小蓝可以拼出 1 到 10, 但是拼 11 时卡片 1 已经只有一张了,不够拼出 11。 现在小蓝手里有 0 到 9 的卡片各 2021 张,共 20210 张,请问小蓝可以从 1 拼到多少? 

#include<iostream>
using namespace std;

//卡片
int num[10];//9种卡片的数量
bool check(int n){
    while(n){
        int i=n%10;
        if(num[i]>0) num[i]--;
        else return false;
        n/=10;
    }
    return true;
}
int main()
{
    for(int i=0;i<10;i++)
      num[i]=2021;
    for(int k=1;;k++){
        if(check(k)==false){
            cout<<k-1<<endl;
            break;
        }
    }
    return 0;
}

平面切分

题目描述

平面上有 N 条直线,其中第 i 条直线是 y = y=Ai*x+Bi​。

请计算这些直线将平面分成了几个部分。

输入描述

第一行包含一个整数 N。

以下 N 行,每行包含两个整数 Ai​,Bi​。

其中,1≤N≤1000,−10^5≤Ai​,Bi​≤10^5。

输出描述

一个整数代表答案。

输入(三条直线相交于一点,不要看坐标轴)

3
1 1
2 2
3 3

输出 

6

 代码:

#include<iostream>
using namespace std;

//平面切分
#include<set>
int n,ans=1;
set<pair<int,int>> line;//用于直线去重
int calc(int a , int b){//计算新增一条直线区域增加数
    set<pair<double , double>>node;//相交点坐标
    for(auto i : line){//遍历容器中的所有元素
        int A = i.first , B = i.second;
        if(A == a) continue ;//两直线平行,进入下次循环
        double x = 1.0 * (B - b) / (a - A);//交点横坐标
        double y = x * a + b;
        node.insert(make_pair(x , y));
    }
    return node.size() + 1;
}
int main()
{
    cin >> n;
    for(int i=1; i<=n; i ++) {
        int a, b;
        cin>>a>>b;
        if(line.find(make_pair(a,b))!=line.end()) continue;//如果输入的有重复
        ans += calc(a, b);
        line.insert(make_pair(a, b));
    }
    cout<<ans<<endl;
    return 0;
}

字串排序

题目描述

小蓝最近学习了一些排序算法,其中冒泡排序让他印象深刻。

在冒泡排序中,每次只能交换相邻的两个元素。

小蓝发现,如果对一个字符串中的字符排序,只允许交换相邻的两个字符,在所有可能的排序方案中,冒泡排序的总交换次数是最少的。

例如,对于字符串 lan 排序,只需要 1 次交换。对于字符串 qiao 排序,总共需要 4 次交换。

小蓝的幸运数字是 V,他想找到一个只包含小写英文字母的字符串,对这个串中的字符进行冒泡排序,正好需要 V 次交换。请帮助小蓝找一个这样的字符串。如果可能找到多个,请告诉小蓝最短的那个。如果最短的仍然有多个,请告诉小蓝字典序最小的那个。请注意字符串中可以包含相同的字符。

输入描述

输入一行包含一个整数 V (1≤V≤10^4),为小蓝的幸运数字。

输出描述

输出一个字符串,为所求的答案。

示例 1

输入

4

输出

bbaa

示例 2

输入

100

输出

jihgfeeddccbbaa

 交换次数=字符串中逆序对数

当给定字符串长度n,最大交换次数:即字母表前n个字母逆序所求次数

 根据上面的公式,对输入的v,从len=1开始枚举,直至maxlen>=v,此时len为所求字符串长度。

 已知长度求最小字典序

我们设最短长度为 len。若 maxlen​=X​,则必然可以通过改变字符顺序构造出长度为 len,逆序对个数为 X−1,X−2,⋯,0 的字符串。

于是我们可以从字符串第 1 个位置开始往后构造,具体为:

从小到大枚举字符 ch(ch∈[a∼z])​​,判断当前位置的字符为 ch​ 是否可以构造出长度为 len​,且逆序对个数大于等于 V​​ 的字符串。

  • 我们设当前位置为 pos,那么 1∼(pos−1) 位置的字符肯定已经构造完成,那么当前位置选择字符 ch 可以新增的逆序对个数就为 1∼(pos−1) 中比 chch 大的字符的个数。
  • 对于 (pos+1)∼len 的位置,我们总共要添加 len−pos 个字符,且要保证这些字符能够产生最多的逆序对。我们可以暴力点一个一个插入。若插入了字符 x,则产生的逆序对个数 = 1∼pos 小于 x 的字符个数) +(len−pos−字符x 的数量)。每次我们选择插入能产生最多逆序对个数的字符,并统计插入后产生的总逆序对个数。

最后若第 pos 个位置的字符为 ch 可以构造出长度为 len,且逆序对个数大于等于 V 的字符串,我们就令第 pos 个位置的字符为 ch,继续构造第 pos+1 位置的字符;否则我们就判断第 pos 个位置的字符为 ch+1 是否可以构造出长度为 len,且逆序对个数大于等于 V​ 的字符串。

时间复杂度为 O(26len2)。(当 V=10000 时,len=145​,所以复杂度完全是 ok 的)

 代码:

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

int v;//交换次数
int len;//符合交换次数的字符串的最小长度
int now;//当前已构造好的一部分字符串的逆序对个数
int cnt[27];//当前位置后续部分包含字母a~z的个数
int sum[27];//当前构造的部分字符串包含字母a~z的个数
int get_max(int len){
    return ((len-(len/26+1))*(len/26+1)*(len%26)+(26-len%26)*(len/26)*(len-len/26))/2;
}
//当前位置上放置的是第x个字母(即'a'+x-1),后面还有n个位置
bool check(int x,int n){
    memset(cnt,0,sizeof(cnt));
    int add1=0;//在当前位置放置x后新增的逆序对个数
    int add2=0;//在当后续位置放置字母时最多新增的逆序对个数
    //统计已经放置好的部分,比x大的字符个数,即add1
    for(int j=26;j>=x+1;j--) add1+=sum[j];
    sum[x]++ ;//在当前位置放置x,则x个数加1
    for(int L=1;L<=n;L++){//考察当前位置后面共n个位置的第L个位置
        int ma=-1;//在后续第L个位置上放字母新增的逆序对数最大值
        int pos=0;
        int num=0;//已经放置的大于第j个字母的字符个数
        //考察字母a~z,优先放更大的字母:越逆序,交换次数越多
        for(int j=26;j>=1;j --){
            if(L-1-cnt[j]+num>ma){//现在还没插入第j个字母
                ma=L-1-cnt[j]+num;
                pos=j;
            }
            num+=sum[j];
        }
        add2+=ma, cnt[pos]++;
    }
    if(now+add1+add2>=v){
        now+=add1;
        return true;
    }
    else {
        sum[x]-- ;
        return false;
    }
}
int main()
{
    string ans="";
    cin>>v;
    //所求字符串长度
    for(int i=1; ;i++){
        if(get_max(i)>=v){
            len=i;
            break ;
        }
    }
    //构造字典序最小的字符串
    for(int i=1;i<=len;i++){
        for(int j=1;j<=26;j++){//在位置i上试探性放置第j个字母
            if(check(j,len-i)){//位置i上放置字母
                ans+=char(j+'a'-1);
                break;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值