A. Vasya and Coins
题意:
给你$a$个一块钱的硬币和$b$个两块钱的硬币,求最大不能被凑出来的正整数
思路:
-
如果$a$为$0$,说明$1$我们无论无何也凑不出来
-
如果$a$存在,说明$[1,a+2*b]$中的任何一个正整数都可以凑出来,答案即为$a+2b+1$
时间复杂度:$Ot$
#define cf int _; cin>> _; while(_--)
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] ;
signed main()
{
cf
{
int a , b ;
cin >> a >> b ;
if(a >= 1)
cout << a + 2 * b + 1 << '\n' ;
else
{
puts("1") ;
}
}
return 0 ;
}
B. Vlad and Candies
题意:
给你$n$个数的数组,每次你可以任选一个最大的数$a[i]$,使其减一
但是不能连续选$2$个下标相同的数,求是否可以都减为$0$
思路:
- 显然当最大值减去次大值$>=2$
说明无论无何也不可以都减为$0$
在证明其他情况一定可以的充要性
- 我们假设最大值为$d1$,次大值为$d2$
我们先减去$d1$,在减$d2$,然后不断重复
在减去的过程中,如果出现了与其他数相等的情况
就依次减去其他相等的数,这样子一定可以保证都可以减为$0$
时间复杂度:$Onlogn$
#define fer(i,a,b) for(int i = a ; i <= b ; ++ i)
#define cf int _; cin>> _; while(_--)
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] ;
signed main()
{
cf
{
cin >> n ;
fer(i,1,n) cin >> a[i] ;
sort(a + 1 , a + 1 + n) ;
if(n == 1)
{
cout << (a[1] == 1 ? "YES" : "NO") << '\n' ;
}
else
{
cout << (a[n] - a[n - 1] >= 2 ? "NO" : "YES") << "\n" ;
}
}
return 0 ;
}
C. Get an Even String
题意:
给你一个字符串,求将其变为$aaccxxffee.....$等任意字母都连续出现偶数个数的情况下你需要删除字母个数的最小值
$n <= 2e5$
思路:
直接求不好求,不如换个思路
- 原题等价于n-最大任意字母都连续出现偶数情况下的字符串长度
求最大长度遍历一遍即可
时间复杂度:$On$
inline void de(auto x) {cout << x << "\n" ;}
#define cf int _; cin>> _; while(_--)
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] ;
char s[N] ;
signed main()
{
cf
{
cin >> s + 1 ;
n = strlen(s + 1) ;
bitset<200> st ;
st[s[1]] = 1 ;
int res = 0 ;
for(int i = 2 ; i <= n ; i ++)
{
if(st[s[i]])
{
res += 2 ;
st.reset() ;
}
else
{
st[s[i]] = 1 ;
}
}
de(n - res) ;
}
return 0 ;
}
D. Maximum Product Strikes Back
题意:
给你一个$n$个数的数组
你可以删去任意数组前缀或者任意数组后缀
求删去之后使剩下的子数组的乘积最大
输出前缀删除的个数以及后缀删去的个数
题目规定空的子数组[]的乘积为1
$n <= 2e5 , |a[i]|<=2$
思路:
一道比较考验码力 的题,不知道有没有更简单的方法
首先题目定义了空的子数组[]的乘积为$1$
说明最后剩下的子数组一定不能出现$0$
根据此性质我们可以把数组分为
$0 ..... 0 ...... 0......0.....0$
特殊定义$a[0] = 0 , a[n + 1] = 0$
对每一个$0.......0$
可以分$2$种情况讨论
- 情况$1$,区间负数个数为偶数,说明此区间乘积必定为正,更新一下最大值
- 情况$2$,区间负数个数为奇数,二分找到这个区间的第一个负数和最后一个负数的下标,更新一下最大值
时间复杂度:$Onlogn$
#define fer(i,a,b) for(int i = a ; i <= b ; ++ i)
#define lb lower_bound
#define ub upper_bound
inline void de2(auto a , auto b) {cout << a << " " << b << "\n" ;}
#define cf int _; cin>> _; while(_--)
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] ;
int s[N] ; // s[i]表示[1,i]中负数的个数
int ss[N] ; // ss[i]表示[1,i]中a[i]绝对值=2的个数
int res , ll , rr ;
void get(int l , int r)
{
int x = s[r] - s[l - 1] ;
int sum = ss[r] - ss[l - 1] ;
if(sum > res && x % 2 == 0)
{
res = sum ;
ll = l - 1 , rr = n - r ;
}
}
signed main()
{
cf
{
cin >> n ;
vector<int> v ;
fer(i,1,n) cin >> a[i] , s[i] = s[i - 1] + (a[i] < 0) , ss[i] = ss[i - 1] + (abs(a[i]) == 2) ;
a[0] = 0 , a[n + 1] = 0 ;
fer(i,0,n+1)
if(a[i] == 0)
v.push_back(i) ;
multiset<int> q ; // set里面放所有负数的下标
fer(i,1,n)
if(a[i] < 0)
q.insert(i) ;
res = -1e9 ;
ll = 1 , rr = n - 1 ;
for(int i = 0 ; i + 1 < v.size() ; i ++)
{
int l = v[i] + 1 , r = v[i + 1] - 1 ;
int x = s[r] - s[l - 1] ;
if(x % 2 == 0)
{
get(l,r) ;
}
else
{
auto it = q.lb(l) ;
get(*it + 1 , r) ;
it = q.ub(r) ;
-- it ;
get(l , *it - 1) ;
}
}
de2(ll , rr) ;
}
return 0 ;
}
E. Matrix and Shifts
题意:
给你$n*n$的数组,你可以进行以下操作任意次
- 将第一行移动到最后一行
- 将最后一行移动到第一行
- 将第一列移动到最后一列
- 将最后一列移动到第一列
问你怎么操作使得
该数组与另外一个n*n的数组(主对角线都是1,其余全为0)不同的数的个数最小
思路:
假设此数组
$1$的总个数为$sum$
与另外一个数组重合的$1$的个数为$res$
那么
- 主对角线其余地方不同的数的个数为$sum-res$
- 主对角线上不同的数的个数为$n-res$
答案即为$sum + n - 2 * res$
因为要最小化结果,$sum,n$都为常数
等价于求$res$的最大值
我们可以枚举向右的偏移量$i$和第$j$行
同时更新$res$的最大值,即为$s[j][(i + j) \% n] == 1$的总个数
时间复杂度:$On^2$
#define fer(i,a,b) for(int i = a ; i <= b ; ++ i)
#define cf int _; cin>> _; while(_--)
inline void de(auto x) {cout << x << "\n" ;}
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
char s[M][M] ;
signed main()
{
cf
{
cin >> n ;
int sum = 0 ;
fer(i,0,n-1)
fer(j,0,n-1)
{
cin >> s[i][j] ;
if(s[i][j] == '1') sum ++ ;
}
int res = 0 ;
fer(i,0,n-1)
{
int k = 0 ;
fer(j,0,n-1)
{
if(s[j][(i + j) % n] == '1')
k ++ ;
}
res = max(res , k ) ;
}
de(n + sum - 2 * res) ;
}
return 0 ;
}
F2. Promising String (hard version)
题意:
给你一个只包含$+$或$-$的字符串
你可以将连续的2个负号变成正号
求有多个子字符串满足
$+$的数量=$-$的数量
思路:
首先我们假设有a个负号,b个正号
将连续的2个负号变成正号
等价于
-
$a-2k=b+k$
-
$a-b=3k$
也就是求有多少个
-
$(a-b) \% 3 == 0$
-
并且$a >= b$
我们假设正号为$-1$,负号为$+1$
$s[i]$数组表示$[1,i]$这个区间的和
等价于求有多少个$[l,r]$
满足
-
$(s[r]-s[l-1]) \% 3 == 0$
-
并且$s[r] >= s[l-1]$
考虑
-
$(s[r]-s[l-1]) \% 3 == 0$
开三个数组即可,因为任何数$\%3$都只有三个取值 -
并且$s[r] >= s[l-1]$
树状数组即可,因为树状数组可以动态统计小于等于这个数的情况下,什么什么的个数,参考逆序对的求法
在求的过程中,会有负数出现,所以还需离散化
时间复杂度:$Onlogn$
#define fer(i,a,b) for(int i = a ; i <= b ; ++ i)
#define all(x) (x).begin(),(x).end()
#define lb lower_bound
#define int long long
#define pb push_back
#define cf int _; cin>> _; while(_--)
inline void de(auto x) {cout << x << "\n" ;}
inline int mo(int a , int b) {return a = (a % b + b) % b ;}
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] , s[N] ;
char g[N] ;
int tr[4][N] ;
int d ;
int lowbit(int x)
{
return x & -x ;
}
int sum(int x)
{
long long res = 0 ;
for(int i = x ; i ; i -= lowbit(i)) res += tr[d][i] ;
return res;
}
void add(int x , int c)
{
for(int i = x ; i <= n + 1 ; i += lowbit(i)) tr[d][i] += c ;
}
vector<int> q ;
int get(int x)
{
return lb(all(q) , x ) - q.begin() + 1 ;
}
signed main()
{
cf
{
cin >> n >> g + 1 ;
fer(i,1,n)
{
s[i] = s[i - 1] + (g[i] == '-' ? 1 : -1) ;
}
fer(i,0,2)
fer(j,0,n + 10)
tr[i][j] = 0 ;
q.clear() ;
fer(i,0,n) q.pb(s[i]) ;
sort(all(q)) ;
q.erase( unique(all(q)) , q.end() ) ;
//fer(i,0,n) de2(s[i] , get(s[i])) ;
int res = 0 ;
fer(i,0,n)
{
d = mo(s[i] , 3) ;
res += sum(get(s[i])) ;
add(get(s[i]), 1) ;
}
de(res) ;
}
return 0 ;
}
顺便添上$F1$的代码
时间复杂度:$On^2$
#define cf int _; cin>> _; while(_--)
inline void de(auto x) {cout << x << "\n" ;}
const int N = 1e6 + 10 , M = 3010 , mod = 1e9 + 7 ; // mod = 998244353 ;
int n , m ;
int a[N] ;
char s[N] ;
signed main()
{
cf
{
cin >> n >> s + 1 ;
int res = 0 ;
for(int i = 1 ; i <= n ; i ++)
{
int s1 = 0 , s0 = 0 ;
for(int j = i ; j <= n ; j ++)
{
if(s[j] == '-') s0 ++ ;
else s1 ++ ;
if(s0 >= s1 && (s0 - s1) % 3 == 0)
res ++ ;
}
}
de(res) ;
}
return 0 ;
}
请问Fenwick求解树状数组为啥不用倒序遍历原数组
正序遍历需要统计小于等于s[r]的个数 倒序遍历需要统计有多少个大于等于s[l-1]的个数 树状数组是可以做到的 不过有些时候求小于等于莫个数的最大值的时候 就不能倒序遍历 所以看个人写法
至于为啥不用 是因为这题正序遍历倒序都可以 我个人比较习惯写正序
D题中get函数并不需要判断负数个数是否为偶数,因为传入的形参范围已经保证了个数为偶数
是的 我写的过程中出现了bug 在debug的时候加上了 不过也无伤大雅