大意:给定一个n长度的排列a。现每次操作能从a中选择两个不同的元素,将两者中较大者移动到a末尾,较小者移动到a首部。问至少要多少次操作,才能使得排列成为字典序最小的排列。
思路:首先我们可以确定的是,最多操作n/2次就能满足题目要求。假设选定了( x , y) ,且x < y,则将(x,y)操作后,至少需要操作( x-1, y+1 ) ,... ( 1 , n ) 次才能完成题目要求。当我们操作(x,y)时,必须保证[ x+1,y-1]是排序好的,才能得到最小的操作次数。故我们先记录每个值x在a中的位置pos[x],然后以n/2 为基准,判断左右是否排完序,找到成功排序的区间长度的一半,即为操作的结果。
(看代码得出的思路,可能有些问题?)
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
#define PII pair<int,int>
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
LL a[N] ,pos[N] ,w[N];
bool st[N] ;
int main()
{
int T ;cin >> T;
while( T--){
int n ;
cin >> n ;
for(int i = 1 ; i <=n ; ++i)
cin >> a[ i] ,pos[a[i]] =i;
int k = n/2;
while( k && pos[k] <pos[k+1] && pos[n-k+1] >pos[n-k]) --k;
cout<<k<<endl;
}
return 0 ;
}
大意: 主持人有四种笑话,第一种笑话能让A,B快乐值+1, 第二种A快乐值+1,B则-1;第三种与第二种相反,第四种则会使两人都-1。 A,B的初始快乐值都是0 ,当有一个人快乐值为负数时则不再讲笑话。现给出四种笑话的数量,问最多能讲多少个笑话?
思路:贪心。首先是A,B都快乐的笑话先讲,然后我们可以通过讲第一种笑话后紧跟着第二种笑话,使得A B的快乐值听完两次后都不变,所以可以反复操作,使得其中一种笑话个数被用光。由于接下来剩余的另一种笑话和第四种笑话都会使某一个人的快乐值减小 , 所以能听的个数则是,剩余笑话个数 和 A或B快乐值 的最小值 。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
#define PII pair<int,int>
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
LL a[N] ,pos[N] ,w[N];
bool st[N] ;
int main()
{
int T ;cin >> T;
while( T--){
int a,b,c,d;
cin >>a >> b >> c >> d ;
if( a== 0 ){
cout<<1 <<endl;continue;
}else{
int res =a + min( b,c) *2 ;
int rest = b + c - 2*min(b,c) + d;
if( rest > a ) res += a +1;
else res += rest;
cout<< res <<endl;
}
}
return 0 ;
}
第三题:1791E - Negatives and Positives
大意:现在有n长度的数组a,每次交互能实现两种操作: 指令为1-会输入l,r,将[ l , r ]种的数a[i]更新为数位之和。比如ai = 123 ,数位之和位1+2+3=6.故更新为6 . 指令为2-输入x , 输出a[x]的值
思路:树状数组模板题。由于涉及到区间修改,显然是维护差分树状数组。由于数位之和在个位数时保持不变,故我们可以先预处理每个元素在处理第k次时的值。然后操作[l,r]时,则令a[l..r]这段区间的操作次数都+1。预处理的时间复杂度是O(cn) ,c最大为2。每次操作的次数都是logn。 故总的时间复杂度是O(nlogn)。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
#define PII pair<int,int>
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
int a[31][N] ,b[N] ,w[N];
bool st[N] ;
int lowbit( int x ){
return x&(-x ) ;
}
int main()
{
int T ;cin >> T;
while( T--){
int n ,q;
cin >> n >>q;
memset( b , 0 ,sizeof b ) ;
for(int i =1; i<=n; ++i){
int x; cin >> x;
a[0][i] = x;
int cnt =1;
for( ; cnt<31; ++cnt){
int t = 0 ;
while( x ) t += x%10 , x/=10;
a[cnt][i] = t;
x= t;
}
}
while( q--){
int cmd;cin >> cmd;
if( cmd == 1) {
int l ,r ;cin >> l >> r ;
for(int i = l; i<=n; i+= lowbit(i)) b[i] += 1;
for(int i = r+1; i<=n ; i+= lowbit( i)) b[i] -=1;
}else {
int x;
cin >> x;
int t = 0 ;
for( int i = x ; i ; i -= lowbit(i)) t += b[i] ;
if( t > 28) t= 28 ;
cout<< a[t ][x] <<endl;
}
}
}
return 0 ;
}
大意:给定一个字符串s, 问字符串(只有小写字母)中,连续两个字符组成的字符串有多少种
思路: 哈希。每个字符都是小写字母, 可把a~z映射成1~26。 比如ab可映射成1*26+2。最后统计哈希表中出现的个数。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
#define PII pair<int,int>
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
int a[31][N] ,b[N] ,w[N];
bool st[N] ;
int lowbit( int x ){
return x&(-x ) ;
}
int main()
{
int T ;cin >> T;
while( T--){
int n ,q;
cin >> n ;
string s; cin >> s;
int d[26*27] ;
memset( d , 0 , sizeof d) ;
for(int i = 0 ; i + 1 < n ; ++i ){
d[ (s[i]-'a')*26 + s[i+1]-'a'] =1;
}
int sum = 0 ;
for(int i = 0; i< 26*27 ; ++ i) if( d[i])++ sum ;
cout<< sum <<endl;
}
return 0 ;
}
大意:给定长度为n的数组a,b, 并给定一个值k,试组合b,使得|ai-bi| < k ,并输出顺序输出b数组。题目数据保证有解。
思路:贪心。对于a的第i小的元素,应当匹配b中的第i小的元素。若ai匹配bj,j>i。则可能使得ai无法找到匹配的元素。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
using namespace std;
typedef long long LL ;
typedef pair<LL,LL> PII ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
LL a[N] , b[N];
PII s[N] ;
int main()
{
int T ;cin >> T;
while( T--){
int n ,q;
cin >> n >>q ;
for(int i = 0 ; i < n ; ++i) cin >> a[i] ;
for(int i = 0 ; i < n; ++i) cin >> b[i] ;
for(int i = 0 ;i <n ; ++i) s[i].x = a[i ], s[i].y = i ;
sort ( s , s + n ) ;
sort( b , b+ n ) ;
for(int i = 0 ; i< n ; ++i) a[ s[i].y ] = b[i] ;
for(int i= 0; i < n ; ++i) cout<< a[i] <<" " ;
cout<<endl;
}
return 0 ;
}
第六题:C - Vlad Building Beautiful Array
大意:给定长度n的数组ai, 现在要求你构造一个数组b, bi可为ai 或者 ai-aj, j 是任意的。要求b的元素都是正整数,且全部元素奇偶性相同。问是否能构造出符合条件的b?
思路: 对于bi,bi可以等于 ai -aj或者ai。当ai是a中最小的元素时,显然不存在ai-aj满足题意。故bi=ai。 也就是说,整个数组的奇偶性,由最小的元素来确定。故我们需要先找到最小的元素。
如果最小的元素x是奇数, 那么a中的奇数直接赋值到b中即可,a中的偶数又比x大,故可以通过a-x得到大于0的奇数赋值给b,故x是奇数时, 必然能构造出b;
若最小的元素x是偶数,如果全部元素都是偶数,显然能直接构造出b。若存在奇数,则对于最小的奇数k,但k减去任何元素都无法满足为正偶数。故存在奇数则无法构造成功。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
int a[N] , b[N];
int main()
{
int T ;cin >> T;
while( T--){
int n ,q;
cin >> n ;
for(int i = 0 ; i < n ; ++i) cin>> a[i] ;
int mmin =a[0] ;
int odd = 0 ;
for(int i =0; i < n ; ++i) mmin = min(mmin , a[i]) ,odd += a[i]&1;
if( mmin &1) {
cout<< "YES "<<endl;
}else {
if( odd) cout<<"NO" <<endl;
else cout<<"YES" <<endl;
}
}
return 0 ;
}
第七题:D - Flipper
大意:给定一个长度为n的数组a。我们可以进行如下操作:选定l<=r , 将a[l..r]的元素反转,然后将[1,l-1]与[r+1,n]的元素都互换位置。现可进行一次操作,请你输出操作后字典序最大的数组。
比如:n=5,a={2,3,1,5,4}。如果选定l=2,r=3,在反转后是a={2,1,3,5,4},[1,l-1]与[r+1,n]即[1,1]与[4,5]的元素互换后是a={5,4,1,3,2}。
思路:当n等于1时,显然直接输出元素即可。
当n大于1时,我们可以得出,无论如何操作,数组的第一个元素都是会被排到后方的。
同时,为了保证字典序最大,我们应该找到从第二个元素开始最大的元素a[k]。
在操作后,将a[k]放到首位。
然后分类讨论,如果a[k]不位于最后一位的话,显然,从a[k]开始往后的元素都会在操作后紧跟着a[k]到数组的前部。
为了翻转,至少要选定a[k]前面 x个数。也就是说翻转的区间至少得是[k-1,k-1],区间的右端点固定为k-1。由于翻转后,区间左端点的元素会在a的第一个元素之前,而为了字典序最大,显然,当左端点左边的元素大于a的第一个元素时,需要拓展左端点。
若a[k]位于最后一位的话,则不需要考虑a[k]后的元素了,可直接把区间设置为[k,k],这样能使得a[k]直接位于a的首部。对于左端点的扩展,方法与上述一致。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ,mod = 1e9 +7 ;
int a[N] , b[N];
int main()
{
int T ;cin >> T;
while( T--){
int n ,q;
cin >> n ;
for(int i = 1; i <=n ; ++i) cin >> a[ i] ;
if( n == 1) {
cout<< a[1] <<endl;continue;
}
int Max = 2;
for(int i = 3; i<=n ; ++i) if( a[Max] < a[i]) Max = i ;
int r = Max;
if( r != n ) --r ;
int l =r ;
while( l>1 && a[l-1] > a[1]) --l;
for(int i = r+1; i <=n ; ++i) cout<<a[i]<< ' ' ;
for(int i = r ; i>= l ; --i) cout <<a[i] <<" " ;
for(int i = 1; i < l ; ++ i ) cout<<a[i] <<" " ;
cout<< endl;
}
return 0 ;
}
大意:给定长度为n的数组a, 并给定k, 现要你求a的子序列b的个数 模 1e9 +7 。b应当满足以下要求:b的长度为m,b中任意元素的差的绝对值小于m,b中每个元素不同。
思路: 由 b中任意元素的差的绝对值小于m 可得, b的最大值与最小值之差小于m。
故我们可先对a进行排序,并用哈希存储a每个元素出现的次数,再对a去重。
那么长度为m的数组b,就相当于数组a的一个长度为m的滑动窗口,且滑动窗口中的最大值与最小值即a[l],a[l+m]的差的绝对值小于m。那么对于符合要求的每个滑动窗口,种类即为m个元素出现次数的乘积。 结果则为所有符合要求的滑动窗口的和。
由于滑动窗口移动过程中会除以左端点,故需要乘以模1e9+7下的逆元。
代码:
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#define x first
#define y second
using namespace std;
typedef long long LL ;
const int N = 2e5 +10 ;
const LL mod = 1e9+7 ;
LL a[N] , b[N];
LL qmi( LL a, LL b){
LL res =1;
while( b) {
if( b&1) res = ( res *a ) % mod;
b >>=1;
a = (a*a) %mod ;
}
return res ;
}
int main()
{
int T ;cin >> T;
while( T--){
int n ,m;
cin >> n>> m ;
map<int,LL > cnt;
for(int i =0 ; i < n ; ++i ) {
cin >> a[i] ;
}
sort( a, a + n ) ;
cnt.clear( ) ;
for(int i = 0 ;i < n ; ++i ) cnt[a[i]] ++ ;
n = unique( a, a+ n) - a;
LL sum = 0 ,cur =1;
for(int r = 0 , l= 0 ; r < n ; ++ r){
//cout<< a[r] <<" " << cnt[a[r]] << ' ' << cur << ' '<<sum<< endl;
cur = ( cur * cnt[ a[r] ]) % mod ;
while( a[r] -a[l] >= m && l< r ){
cur = ( cur * qmi( cnt[ a[l] ],mod-2 )) %mod ;
l++;
}
if( r-l+1 == m ) {
//cout<<"add "<< sum << " " << cur <<endl;
sum = ( sum + cur) %mod; cur = ( cur * qmi( cnt[ a[l++] ],mod-2 )) %mod;
}
}
cout<< sum <<endl ;
}
return 0 ;
}