正文
题目1
题目链接
题目大意:
给出整数x,求两个整数a和b,满足:
(,)+(,)= .
GCD是最大公约数;
LCM是最小公约数;
题目保证a和b存在;
输入:
第一行整数t,表示样例个数; (1≤≤100)
接下来t个样例,每个样例一行,整数x;(2≤≤10^9)
输出:
整数a和b; (要求范围,1≤,≤10^9)
Examples
input
2
2
14
output
1 1
6 4
题目解析:
构造题,这里提供一种方法:1+(x-1)。
构造题,想到就简单;想不到就很难。
如果不能马上想到规律,可以尝试将数字设置小一些,比如说x=10;
再多考虑x=11、x=12这些,探索规律,然后将x=100进行验证。
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
cout << 1 << " " << n - 1 << endl;
}
题目2 CopyCopyCopyCopyCopy
题目链接
题目大意:
给出n个整数的数组,现在将数组复制,放在数组末尾,重复n次;
求新的数组中,最长严格递增子序列的长度是多少?
输入:
第一行整数t,表示样例个数; (1≤≤100)
接下来t个样例,每个样例2行;
第一行,整数 (1≤≤10^5)
第二行,n个整数 1, 2, …, (1≤≤10^9)
输出:
每个样例一行,表示最长递增子序列的长度;
Examples
input
2
3
3 2 1
6
3 1 4 1 5 9
output
3
5
样例解释,3,2,1重复3次,得到[3,2,1,3,2,1,3,2,1] 最长递增子序列是[1,2,3],长度是3;
题目解析:
如果n个数字不相同,重复n次之后,必然可以选出n个数字,最长递增子序列的长度是n;(因为重复n次,每次选1个数字就好)
如果n个数字中存在相同,则相同的数字只能取一个;
所以答案就是数组中,数字不同的个数。
转换思路,题目变成求数组中不同数字的数量。
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
int ans = 0;
map mp;
for (int i = 0; i < n; ++i) {
int k;
cin >> k;
if (!mp[k]) {
mp[k] = 1;
++ans;
}
}
cout << ans << endl;
}
题目3 Numbers Exchange
题目链接
题目大意:
小明喜欢一种字符串Zebras,Zebras的定义是以0开头,以0结束,中间0/1交替出现,比如说:0, 010, 01010;
类似1, 0110, 0101这种就不是Zebras;
现在给出一串长度为n的字符串(全部是01组成);
小明希望把字符串分成若干个子序列,并且每个子序列都是Zebras;(每个字符存在子序列中,且只在一个)
如果不能,则输出-1;
输入数据:
一串字符串,长度不超过20w;
输出数据:
第一行,数字k,表示分为k个子序列;
Examples
input
0010100
output
3
3 1 3 4
3 2 5 6
1 7
样例解释:
分3个子序列010,010,0;
题目解析:
由贪心知道,01010..的序列越长越好;(0的利用率提升)
题目的要求是每个字符都要放到子序列里去,那么有一种简单的做法:
每次从左到右扫描,在未选择的字符中,寻找最长的Zebras,比如说对样例0010100处理:
第一次扫描,扫出01010;(1,3,4,5,6)
第二次扫描,扫出0;(2)
第三次扫描,扫出0;(7)
但是这样的代价比较高,是N^2级别复杂度;(对于00001111000这样的数据)
考虑这样一种优化方案:
有两个队列x、y,队列x是0结束的子序列,队列y是1结束的子序列;
当遇到0时,从队列y取一个子序列,末尾拼0然后放入队列x;如果队列y没有数据,则新建一个子序列(因为0可以是单独的序列),然后将子序列放入队列x;
当遇到1时,从队列x取一个子序列,末尾拼1然后放入队列y;如果队列x没有数据,则无解;
这样每次都是O(1)的操作,总体的复杂度是O(N);
cin >> str;
size_t n = strlen(str);
queue q[2];
int id = 0;
int ok = 1;
for (int i = 0; i < n && (ok>0); ++i) {
if (str[i] == '0') {
if (q[1].empty()) {
ans[id].push_back(i);
q[0].push(id++);
}
else {
int x = q[1].front();
q[1].pop();
ans[x].push_back(i);
q[0].push(x);
}
}
else {
if (q[0].empty()) {
ok = -1;
}
else {
int x = q[0].front();
q[0].pop();
ans[x].push_back(i);
q[1].push(x);
}
}
}
if (ok > 0 && q[1].size() == 0) {
cout << id << endl;
for (int i = 0; i < id; ++i) {
cout << ans[i].size() << " ";
for (int j = 0; j < ans[i].size(); ++j) {
cout << ans[i][j] + 1 << " ";
}
cout << endl;
}
}
else {
cout << -1 << endl;
}
题目4 A Leapfrog in the Array
题目链接
题目大意:
有若干个格子排成一行序号是1、2、3、4、5、、、,有1~n的n个整数分别放在格子里,第i个数字放在第2i-1格子;
现在对数字进行操作:
每次把最右边的数字x,放在x左边最接近的空格里;
不断重复,直到x左边没有空的格子;
现在有q个询问,每个询问是数字x[i],求第x[i]个格子上面的数字;
输入数据:
n and q (1 ≤ n ≤ 1e18, 1 ≤ q ≤ 200 000)
接下来q行,每行一个数字x[i];
输出数据:
对于每个询问,输出第x[i]个格子上面的数字;
Examples
input
4 3
2
3
4
output
3
2
4
样例解释,见上图。
题目解析:
从题目的操作结果来看,最后操作停止的结果必然是n个数字分布在第1到n个格子里;
容易知道,小于等于n/2的数字是不会动的。(因为数字的左边没有格子)
相当于后面(x - n/2)数字排列紧密(没有空格),然后数字的最后一个跳到前面;
于是可以用递归的思想解决问题,不断重复,直到数字剩下1个。
思考:
另一种解法:
我们用0表示空格,当n=8的时候,原序列为1,0,2,0,3,0,4,0,5,0,6,0,7,0,8
把每次移动的数字写出来:
8,8,7,8,6,7,5,8,4,6,3,7,2,5,1。
或许看不出来问题?
试试这么看:
8,
8,7
8,6,7,5,
8,4,6,3,7,2,5,1
每次都是从n-2^(i-1)开始,按顺序插入上一行的数字。
从题意知道,每次都会填补数字最左边的一个空格。
又根据移动的性质,我们知道填补第n个格子数字不会再移动。(因为当第n个格子成为最右边的数字时,左边已经没有格子)
所以只需要算出n个数中间的格子是倒数第k个空格,从上面的序列选择第k个数字,就是n个数字移动后最右边一个数字。
/**
1 2 3 4 5 6 按照原来的规则进行选择,最右边的一个
@param n 前n个
@return 最右边的一个
*/
lld solve(lld n) {
if (n % 2) {
return (n + 1) / 2;
}
return n / 2 + solve(n - n / 2);
}
int main(int argc, const char * argv[]) {
// insert code here...
lld n, q;
cin >> n >> q;
while (q--) {
lld x;
cin >> x;
if (x % 2 == 1) {
cout << (x + 1) / 2 << endl;
}
else {
cout << solve(n - x / 2) + x / 2 << endl;
}
}
return 0;
}
总结
题目1 是简单构造题,构造题也有一些思考门路,从小样例开始分析规律;
题目2 是考察思路,代码很简单,核心在于有没有想到题目其实就是求数组的不同数字;
题目3 是考察实现能力,可能方法会有很多,但是需要找一个简单快捷方案,并且能输出答案;
题目4 看似很复杂,其实考虑下n=3、n=4、n=5,模拟下操作可以发现规律。