卡片
题目描述
小蓝有很多数字卡片,每张卡片上都是数字 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;
}