下午茶题解
2022.6.27 构造mex序列
- \(ans=\min(mex(nums[l_{i}]\dots nums[r_{i}]))\\=\min(r_{i}-l_{i}+1)\)
- 我们可以知道对于每个区间\([l_{i},r_{i}]\),该区间的\(mex\)值最大为\(r_{i}-l_{i}+1\)
- 所以我们只需要遍历一遍区间先求出\(ans\)
- 接着构造数组时只需要按照\([0,ans)\)循环放入数组即可。
#define inf 0x3f3f3f3f
int n, m;
int ans=inf;
void solve() {
vector<int> nums(n + 1);
while (m--) {
int left = read(), right = read();
ans = min(ans, right - left + 1);
}
printf("%d\n", ans);
for (int i = 1; i <= n; i++) {
printf("%d ", i % ans);
}
}
signed main() {
n = read(), m = read();
solve();
return 0;
}
2022.6.29 处理数组
- 本题思路挺好想的,就是实现起来细节特别多
- 如果有奇数个负数并且有\(0\),就用\(0\)把多出来的负数消化掉
- 如果有没有\(0\),那么就删除多余的负数
- \(0\) 不管有多少个,都可以内部消化成一个,最后删除剩下的 \(0\) 即可(如果这不是最后一个操作的话)。
int n, m;
int ans;
void solve() {
vector<int> nums(n + 1);
vector<int> idx_neg, idx_zero, idx_posi;
int cnt_neg = 0, cnt_zero = 0;
for (int i = 1; i <= n; i++) {
nums[i] = read();
if (nums[i] == 0) {
cnt_zero++;
idx_zero.emplace_back(i);
}
else if (nums[i] < 0) {
cnt_neg++;
idx_neg.emplace_back(i);
}
else {
idx_posi.emplace_back(i);
}
}
sort(idx_neg.begin(), idx_neg.end(), [&nums](int a, int b) {return nums[a] > nums[b]; });
int last = 0;
int cnt = 0;
if (cnt_zero) {
for (int i = 0; i < cnt_zero; i++) {
if (last) {
printf("1 %d %d\n", last, idx_zero[i]);
cnt++;
}
last = idx_zero[i];
}
if (cnt_neg % 2) {
if (last) { //if there is zero, then we should process the delete program
printf("1 %d %d\n", idx_neg[0], last);
cnt++;
if (cnt < n - 1)printf("2 %d\n", last);
}
last = 0;
for (int i = 1; i < cnt_neg; i++) {
if (last) {
printf("1 %d %d\n", last, idx_neg[i]);
}
last = idx_neg[i];
}
}
else {
if (last) { //if there is zero
if (cnt < n - 1)printf("2 %d\n", last);
}
last = 0;
for (int i = 0; i < cnt_neg; i++) {
if (last) {
printf("1 %d %d\n", last, idx_neg[i]);
}
last = idx_neg[i];
}
}
}
else { //no zero exists
if (cnt_neg % 2) {
printf("2 %d\n", idx_neg[0]);
last = 0;
for (int i = 1; i < cnt_neg; i++) {
if (last) {
printf("1 %d %d\n", last, idx_neg[i]);
}
last = idx_neg[i];
}
}
else {
last = 0;
for (int i = 0; i < cnt_neg; i++) {
if (last) {
printf("1 %d %d\n", last, idx_neg[i]);
}
last = idx_neg[i];
}
}
}
if (idx_posi.size()) {
int l = idx_posi.size();
for (int i = 0; i < l; i++) {
if (last)printf("1 %d %d\n", last, idx_posi[i]);
last = idx_posi[i];
}
}
}
2022.6.30 字符串进位
- 求两个字符串字典序之间的字符串,模拟\(26\)位进制数加\(1\)即可。
string a,b;
void solve(){
int len=a.size();
int r=len-1;
while(a[r]=='z'){
a[r--]='a';
}
a[r]++;
if(a>=b)cout<<"No such string";
else cout<<a;
}
2022.7.4 最大交替字符串
- 每次可以进行的操作是
s=s[:k][::-1]+s[k:][::-1]
- 我们可以发现,进行这样一次操作之后,字符串的实际顺序并没有改变,只是将开头和结尾改变了。即,我们可以将该字符串视作一个环。从头到尾统计一遍交替子串即可。
- 注意统计偶数串时可能会将整个溢出字符串长度。
void solve() {
s += s;
int len = s.size();
int cnt = 1;
for (int i = 1; i < len; i++) {
if (s[i] == s[i - 1]) {
cnt = 1;
}
else {
cnt++;
ans = max(ans, cnt);
}
}
cout << ans;
}
2022.7.6 和为3倍数的数组
- \(dp[i][0|1|2]\) :表示以 \(nums_{i}\) 结尾和模 \(3\) 余 \(0|1|2\) 的数组个数。
- \([l,r]\) 区间内模 \(3\) 余 \(0|1|2\) 的个数都可以 \(O(1)\) 求出。
func solve(){
l,r:=m,k
dp:=make([][]int64,n+1)
for i:=range dp{
dp[i]=make([]int64,3)
}
mod:=make([]int64,3)
mod[0]=r/3-(l-1)/3
dp[1][0]=mod[0]
mod[1]=(r+2)/3-(l+1)/3
dp[1][1]=mod[1]
mod[2]=(r+1)/3-l/3
dp[1][2]=mod[2]
for i:=2;int64(i)<=n;i++{
dp[i][0]=((dp[i-1][0]*mod[0])%MOD+(dp[i-1][1]*mod[2])%MOD+(dp[i-1][2]*mod[1])%MOD)%MOD
dp[i][1]=((dp[i-1][0]*mod[1])%MOD+(dp[i-1][1]*mod[0])%MOD+(dp[i-1][2]*mod[2])%MOD)%MOD
dp[i][2]=((dp[i-1][0]*mod[2])%MOD+(dp[i-1][1]*mod[1])%MOD+(dp[i-1][2]*mod[0])%MOD)%MOD
}
Fprint(w,dp[n][0]%MOD)
return
}
2022.7.7 function max
- 题目要求求出:\(\max(f(l,r)),\ f(l,r)=\sum_{i=l}^{r-l}\vert nums_{i}-nums_{i+1} \vert \times (-1)^{i-l}\)
我们可以轻松观察出,相邻的子段在求和时符号是相反的,由此可以定义状态:\(dp[i][0|1]\)分别表示以\(nums[i]\)结尾且该元素在子段和中的贡献正负。- \(dp[i][0]=\max(dp[i-1][1]-dif[i],-dif[i]) \\ dp[i][1]=\max(dp[i-1][0]+dif[i],dif[i])\)
void solve() {
vector<int> nums(n+1);
for(int i=1;i<=n;i++){
nums[i]=read();
}
vector<int> dif(n+1);
vector<vector<int>> dp(n+1,vector<int>(2)); //0 for neg, 1 for positive
for(int i=1;i<n;i++){
dif[i]=abs(nums[i]-nums[i+1]);
}
int ans=0;
for(int i=1;i<=n;i++){
dp[i][0]=max(dp[i-1][1]-dif[i],-dif[i]);
dp[i][1]=max(dp[i-1][0]+dif[i],dif[i]);
ans=max(ans,max(dp[i][0],dp[i][1]));
}
printf("%lld",ans);
}
(周赛脑筋急转弯题) 移动字符
- 这是一道脑筋急转弯题,主要思路是在两字符串内部顺序相同情况下判断是否有错位的字符无法用交换匹配。
func solve(){
var l int
Fscan(r,&l)
Fscan(r,&s,&t)
if strings.ReplaceAll(s,"b","") !=strings.ReplaceAll(t,"b",""){
Fprintln(w,"NO")
return
}
// 'a' means move_right
// 'c' means move_left
ptr_1,ptr_2:=0,0
for ptr_1<l{
if s[ptr_1]=='b'{
ptr_1++
continue
}
if t[ptr_2]=='b'{
ptr_2++
continue
}
if s[ptr_1]=='a' && ptr_1>ptr_2{
Fprintln(w,"NO")
return
}
if s[ptr_1]=='c' && ptr_1<ptr_2{
Fprintln(w,"NO")
return
}
ptr_1++
ptr_2++
}
Fprintln(w,"YES")
return
}
2022.7.8 计算交替子串
- 由题意可知,该子串只可能是:\(a\),\(ab\dots a\)形式
- 这样我们就可以得出转移方程\(dp[i]=dp[last[b]]+1\)
void solve() {
int len=s.size();
int last=0;
for(int i=0;i<len;i++){
if(s[i]=='a'){
ans=(ans+last+1)%MOD;
}
if(s[i]=='b'){
last=ans;
}
}
printf("%lld",ans);
}
2022.7.12 模拟走路(模拟题)
- 先走大步直到走大步不能满足剩余步数为止。
- 接下来走中步一步,使得剩余步数与剩余距离正好相等(一次一步),即走 \(dis-(turn-1)\) 步。
- 剩下抖动走一次一步。
func solve(){
// n,m,k: number of houses; moving turn; moving distance
if (n-1)*m<k || k<m{
Fprint(w,"NO")
return
}
Fprintln(w,"YES")
cur:=1
for m>0 && k-(n-1)>=m-1{
if cur%2==1{
Fprint(w,n," ")
}else{
Fprint(w,1," ")
}
cur++
k-=(n-1)
m--
}
if m==0{
return
}
pos:=int64(0)
if cur%2==0{
pos=n-(k-m+1)
k-=(k-m+1)
Fprint(w,pos," ")
}else{
pos=1+(k-m+1)
k-=(k-m+1)
Fprint(w,pos," ")
}
for ;k>0;k--{
if k%2==1{
pos--
Fprint(w,pos," ")
}else{
pos++
Fprint(w,pos," ")
}
}
return
}
2022.7.13 按要求构造n叉树
- 题目给出的两个参数:
- 直径:树上任意两个结点的最大距离
- 高度:结点1与任意节点的最大距离
- 我们首先判断给出的条件能否建树:
- 直径不能超过高度的两倍(这个易证)
- 特判:\(d=1,n>2\)的情况,该情况无法满足
- 接下来我们先构造一条\([1,h+1]\)的链
- 如果直径大于高度,则继续构造一条\([h+2,d+1]\)的链
- 最后将多余的叶子挂在结点\(h\)上
func solve(){
if d>2*h || (d==1 && n!=2){
Fprint(w,-1)
return
}
for i:=2;i<=h+1;i++{
Fprintln(w,i-1,i)
}
if d>h{
Fprintln(w,1,h+2)
for i:=h+3;i<=d+1;i++{
Fprintln(w,i-1,i)
}
}
for i:=d+2;i<=n;i++{
Fprintln(w,h,i)
}
return
}
有趣的位运算题
- 我们要求出\(mex(n \bigoplus m), m\in \lbrace0,\dots ,m\rbrace\)
- 我们又知道一个奇妙的小结论:\(n \bigoplus m=k \iff n \bigoplus k=m\)
- 这样,我们的问题就可以转化为求一个最小的\(k\),使得\(n \bigoplus k \notin \lbrace 0,\dots,m \rbrace\),即\(n \bigoplus k \geq m+1\)
- 于是,我们可以用贪心策略遍历\(n\)的每个二进制位,构造最小的\(k\)(注意:可以这样构造是因为\(\bigoplus\)是不进位加法)
void solve() {
n=read(),m=read();
ans=0;
m++;
for(int i=30;i>=0;i--){
if(n>>i&1 and !(m>>i&1))break;
if(!(n>>i&1) and m>>i&1)ans|=1<<i;
}
printf("%lld\n",ans);
}
2022.7.14 构造24点
- 蛮简单的,大于\(5\)时分为奇偶两种情况,具体看代码
void solve() {
if(n<4){
printf("NO");
return;
}
printf("YES\n");
if(n%2==0){
int dif=n-4;
for(int i=n;i>n-dif;i-=2){
printf("%lld - %lld = 1\n",i,i-1);
printf("1 * %lld = %lld\n",i-2,i-2);
}
int tmp=1;
for(int i=1;i<4;i++){
int t=tmp*(i+1);
printf("%lld * %lld = %lld\n",tmp,i+1,t);
tmp=t;
}
}else{ //1,2,3,4,5 4*(5+1)*(3-2)
int dif=n-5;
for(int i=n;i>n-dif;i-=2){
printf("%lld - %lld = 1\n",i,i-1);
printf("1 * %lld = %lld\n",i-2,i-2);
}
printf("5 + 1 = 6\n");
printf("3 - 2 = 1\n");
printf("4 * 1 = 4\n");
printf("4 * 6 = 24\n");
}
return;
}
2022.7.15 构造帽子数组
- 由题意知 \(a_{i}\) 表示与自己不同种类的人数,那么我们可以知道 \(n-a_{i}\) 就表示该种类帽子的人数。
- 那么我们把所有种类人数加起来判断是否为 \(n\) ,是不是这样就可以把数组构造出来了?并不行。
- 我们发现这样一组数据很明显是可以构造的:
4
2 2 2 2
1 1 2 2
- 这给了我们一个启发,我们只要满足\(cnt_{a_{i}}\bmod (n-a_{i})=0, i\in [0,n)\)就可以构造了。
- 为了方便,我们可以在记录过程中就对标记进行更新。
func solve(){
a,b:=make([]int,n),make([]int,n)
tmp:=0
record,cnt:=map[int]int{},map[int]int{}
for i:=0;i<n;i++{
Fscan(r,&a[i])
cnt[a[i]]++
if !containKey(cnt,a[i]) || cnt[a[i]]%(n-a[i])==1{
tmp++
record[a[i]]=tmp
}
b[i]=record[a[i]]
}
for key,val:=range cnt{
if val%(n-key)>0{
Fprint(w,"Impossible")
return
}
}
Fprintln(w,"Possible")
for i:=0;i<n;i++{
Fprint(w,b[i]," ")
}
}
2022.7.17 洛谷月赛div2-1:构造字符串
- 这题真的好难想,画折线图有助于理清思路,将
l
记为\(-1\),r
记为\(1\),先要构造一段区间和为\(0\)的字符串,而且要保证后面构造的字符不能使符合要求的字符串长度增大。 - 注意点:特判\(k=2\)和\(k=1\)情况。
void solve() {
if(k==1){
for(int i=1;i<=n;i++){
if(i%2)printf("l");
else printf("r");
}return;
}
if(k==2){
m-=2;
for(int i=1;i<=m;i+=2){
printf("lr");
}
for(int i=m+1;i<=n;i++){
if((i-m)%(k+1))printf("l");
else printf("r");
}return;
}
for(int i=1;i<=m;i+=2){
printf("lr");
}
for(int i=m+1;i<=n;i++){
if((i-m)%k)printf("l");
else printf("r");
}
return;
}
2022.7.18 炸砖块
- 由于每次只能炸掉外部砖头,那么我们就能知道改变柱子高度的方法只有三种:
- 左侧柱子没了,直接爆破
- 右侧柱子没了,直接爆破
- 高度减一(自己从上到下慢慢刮痧)
- \(dp[i]=\min(dp[i-1]+1,height[i],dp[i+1]+1)\)
func solve(){
nums,dp:=make([]int,n+1),make([]int,n+1)
for i:=1;i<=n;i++{
Fscan(r,&nums[i])
}
for i:=1;i<=n;i++{
dp[i]=min(dp[i-1]+1,nums[i])
}
for i:=n-1;i>=0;i--{
dp[i]=min(dp[i+1]+1,dp[i])
}
ans:=0
for i:=1;i<=n;i++{
ans=max(ans,dp[i])
}
Fprit(w,ans)
}
2022.7.19 构造指定数量连通块
- 状压dp:\(dp[i][j][0|1|2|3]\) 表示以第 \(i\) 列 \(0|1|2|3\) 状态为结尾的图块有 \(j\) 个连通块。
\(dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1]+dp[i-1][j][2]+dp[i-1][j-1][3]\) - 接下来有类似状态转移方程。
void solve() {
int mod = 998244353;
vector<vector<vector<int>>> dp(2, vector<vector<int>>(k + 5, vector<int>(4)));
dp[1][1][0] = 1, dp[1][2][1] = 1, dp[1][2][2] = 1, dp[1][1][3] = 1;
for (int ii = 2; ii <= n; ii++) {
int i = ii % 2;
for (int j = 1; j <= k; j++) {
dp[i][j][0] = (dp[!i][j][0] % mod + dp[!i][j][1] % mod + dp[!i][j][2] % mod + dp[!i][j - 1][3] % mod) % mod;
if(j>1)dp[i][j][1] = (dp[!i][j - 1][0] % mod + dp[!i][j][1] % mod + dp[!i][j - 2][2] % mod + dp[!i][j - 1][3] % mod) % mod;
if(j>1)dp[i][j][2] = (dp[!i][j - 1][0] % mod + dp[!i][j - 2][1] % mod + dp[!i][j][2] % mod + dp[!i][j - 1][3] % mod) % mod;
dp[i][j][3] = (dp[!i][j - 1][0] % mod + dp[!i][j][1] % mod + dp[!i][j][2] % mod + dp[!i][j][3] % mod) % mod;
}
}
ans = (dp[n % 2][k][0] % mod + dp[n % 2][k][1] % mod + dp[n % 2][k][2] % mod + dp[n % 2][k][3] % mod) % mod;
printf("%lld", ans);
}
CF#808 div2 Different operations
- Conclusion:\(nums_{i}\bmod nums_{1} \neq 0 , 2\leq i \leq n\)
- Proof:
- \(nums_{2}\) must be multiple of \(nums_{1}\) .Otherwise \(nums_{2}\) cannot become zero.
- So \(nums_{3}\) must be multiple of \(nums_{1}\) . Otherwise \(nums_{3}\) cannot become zero.
- \(\dots\)
func solve(){
Fscan(r,&n)
nums:=make([]int,n)
for i:=0;i<n;i++{
Fscan(r,&nums[i])
}
for i:=1;i<n;i++{
if nums[i]%nums[0]!=0{
Fprintln(w,"NO")
return
}
}
Fprintln(w,"YES")
return
}
2022.7.20 Phone number
- 思路简单,重点是判重。
s=input()
n=len(s)
dp=[[0]*10 for _ in range(n+1)]
for i in range(10):
dp[1][i]=1
for i in range(2,n+1):
t=int(s[i-1])
for j in range(10):
tmp=t+j
if tmp%2:
dp[i][tmp//2+1]+=dp[i-1][j]
dp[i][tmp//2]+=dp[i-1][j]
else:
dp[i][tmp//2]+=dp[i-1][j]
dif=0 if len(s)>1 else -1
for i in range(1,n):
if abs(int(s[i])-int(s[i-1]))>1:break
if i==n-1:dif=-1
print(sum(dp[n][i] for i in range(10))+dif)
2022.6.21 RGB substring
- 对于三种模式串,先预处理求前缀和,再找到最小改动窗口。
func solve(){
Fscan(r,&n,&k)
Fscan(r,&s)
ans:=int(0x3f3f3f3f)
t="RGB"
pre:=make([][]int,3)
for i:=0;i<3;i++{
pre[i]=make([]int,n+1)
}
for i:=0;i<n;i++{
for j:=0;j<3;j++{
if s[i]!=t[(i+j)%3]{
pre[j][i+1]=pre[j][i]+1
}else{
pre[j][i+1]=pre[j][i]
}
}
}
for i:=0;i+k<=n;i++{
for j:=0;j<3;j++{
ans=min(ans,pre[j][i+k]-pre[j][i])
}
}
Fprintln(w,ans)
return
}
2022.7.22 Remove the substring
- 要删除最长连续序列使要求串任为子串,那么我们可以删除的只有三种情况:
- \(s\) 开头到最晚可以完成匹配的开头。
- 最早出现的 \(t_{i}\) 和最晚出现的 \(t_{i+1}\) 之间的串。
- 最早出现的可以完成匹配的结尾到 \(s\) 的结尾。
- 具体实现可以标记前后缀。
s=input()
t=input()
len_s,len_t=len(s),len(t)
pre,back=[],[0]*(len_t)
ptr=0
for i in range(len_s):
if t[ptr]==s[i]:
pre.append(i)
ptr+=1
if ptr>=len_t:
break
ptr=len_t-1
for i in range(len_s-1,-1,-1):
if s[i]==t[ptr]:
back[ptr]=i
ptr-=1
if ptr==-1:break
ans=max(back[0],len_s-pre[len_t-1]-1)
for i in range(len_t-1):
ans=max(ans,back[i+1]-pre[i]-1)
print(ans)
2022.7.25 Fish weight
- 由于🐟的重量非递减,那么我们要帮的那一方就可以用捞的🐟把要坑的那一方的好🐟给比下去。
- 一开始特判 \(n>m\) 情况就行了。
void solve() {
if(n>m){
printf("YES");
return;
}
vector<int64> fish_1(n), fish_2(m);
for (int i = 0; i < n; i++) {
fish_1[i] = read();
}
for (int i = 0; i < m; i++) {
fish_2[i] = read();
}
sort(fish_1.begin(), fish_1.end());
sort(fish_2.begin(), fish_2.end());
int ptr = n - 1;
for (int i = m - 1; i >= 0; i--) {
if (fish_2[i] < fish_1[ptr]) {
printf("YES");
return;
}
else {
ptr--;
if (ptr < 0)break;
}
}
printf("NO");
return;
}
2022.7.26 Three bags
- 有两种操作方式可能得到最大结果:
- 从两个集合各选出一个数分别减掉其余所有元素,再用大的减小的。易推出这种方法得到的结果为 \(sum_{1}+sum_{2}-sum_{3}\) (下标可换)。
- 取两个集合的最小元素 \(a, b\) ,再从最后一个集合任意选一个元素 \(c\) ,用 \(a,b\) 减去除 \(c\) 外所有元素,用 \(c\) 减去 \(a,b\) 即得到 \(sum_{1}+sum_{2}+sum_{3}-2*\min(nums_{i,min}+nums_{j,min})\)
void solve() {
vector<int64> nums_1(n),nums_2(m),nums_3(k);
int64 m_1,m_2,m_3,sum_1,sum_2,sum_3;
m_1=m_2=m_3=inf;
sum_1=sum_2=sum_3=0;
for(int i=0;i<n;i++){
nums_1[i]=read();
m_1=min(m_1,nums_1[i]);
sum_1+=nums_1[i];
}
for(int i=0;i<m;i++){
nums_2[i]=read();
m_2=min(m_2,nums_2[i]);
sum_2+=nums_2[i];
}
for(int i=0;i<k;i++){
nums_3[i]=read();
m_3=min(m_3,nums_3[i]);
sum_3+=nums_3[i];
}
ans=max(ans,sum_1+sum_2-sum_3);
ans=max(ans,sum_1+sum_3-sum_2);
ans=max(ans,sum_2+sum_3-sum_1);
ans=max(ans,sum_1+sum_2+sum_3-2*min(m_1+m_2,min(m_1+m_3,m_2+m_3)));
printf("%lld",ans);
return;
}
Amazon web service
- 字符串贪心题:遍历字符串 \(a\) ,对它进行最小字典序变化(将最靠后的比 \(s_{i}\) 的字符换到 \(i\) 位置)。
void solve() {
string tmp;
cin>>s>>tmp;
int len=s.size();
for(int i=0;i<len;i++){
int pos=i;
for(int j=i+1;j<len;j++){
if(s[j]<=s[pos]){
pos=j;
}
}
swap(s[i],s[pos]);
if(s<tmp){
cout<<s<<endl;
return;
}
swap(s[i],s[pos]);
}
cout<<"---"<<endl;
return;
}
2022.7.28 Treasure
- 括号平衡变式题:正着遍历一次,变负就寄;再倒着遍历,检查在最后一个
#
出现之后的(
会不会多出来,多出来就寄。 - 输出的话贪心就好,让最后一个
#
补足所缺的)
func solve(){
n:=len(s)
bal,cnt:=0,0
for i:=0;i<n;i++{
if s[i]=='('{
bal++
}else if s[i]==')'{
bal--
}else{
cnt++
bal--
}
if bal<0{
Fprint(w,-1)
return
}
}
ans:=bal
bal=0
for i:=n-1;i>=0;i--{
if s[i]=='#'{
break
}
if s[i]==')'{
bal++
}else{
bal--
}
if bal<0{
Fprint(w,-1)
return
}
}
for i:=1;i<cnt;i++{
Fprintln(w,1)
}
Fprint(w,ans+1)
return
}
2022.7.29 Geometry horse
- 春春的模拟:排序+贪心小小的模拟一下就行了。
- 注意点:普通快读板子爆了。
void solve() {
vector<pll> monsters;
for (int i = 0; i < n; i++) {
int64 num,f;
qr(num),qr(f);
monsters.epb(mkp(f, num));
}
t = read();
vector<int64> fac(t + 2);
for (int i = 1; i <= t; i++) {
qr(fac[i]);
}
fac[t + 1] = LLONG_MAX;
sort(monsters.begin(), monsters.end());
int i = 0, ptr = 1;
int64 target = fac[1];
while (i < n) {
if (monsters[i].s_ <= target) {
ans += (int64)monsters[i].f_ * (int64)ptr * (int64)monsters[i].s_;
target -= monsters[i].s_;
i++;
}
else {
ans += (int64)monsters[i].f_ * (int64)ptr * (int64)target;
monsters[i].s_ -= target;
target = fac[ptr + 1] - fac[ptr];
ptr++;
}
}
printf("%lld", ans);
return;
}
2022.8.1 maximum sum of product
- 本题要求找到一个区间翻转使得 \(\sum_{i=0}^{n} a_{i}*b_{i}\) 最大。
- 从区间dp考虑,我们的大区间可以从小区间向两边拓展转移过来,具体来说,\(dp_{l,r}=\max(dp_{l,r},dp_{l+1,r-1}+(a_{l}-a_{r})*(b_{r}-b_{l}))\) 。
func solve(){
dp:=make([][]int64,n)
a,b:=make([]int64,n),make([]int64,n)
for i:=0;i<n;i++{
Fscan(r,&a[i])
}
sum:=int64(0)
for i:=0;i<n;i++{
Fscan(r,&b[i])
sum+=a[i]*b[i]
}
//dp[l][r]=dp[l+1][r-1]-a[l]*b[l]-a[r]*b[r]+a[l]*b[r]+a[r]*b[l]
for i:=0;i<n;i++{
dp[i]=make([]int64,n+1)
dp[i][i]=sum
if i>=1{
dp[i][i-1]=sum
}
}
ans:=sum
for len:=1;len<n;len++{
for l:=0;l+len<n;l++{
r:=l+len
dp[l][r]=max(dp[l+1][r-1]+(a[l]-a[r])*(b[r]-b[l]),dp[l][r])
ans=max(ans,dp[l][r])
}
}
Fprint(w,ans)
return
}
2022.8.2 Mysterious crime
- 用一个 \(pos_{i,j}\) 数组记录第 \(i\) 个数组中值为 \(j\) 的数的下标。
- 接下来用的是递推的思想,\(len_{i}\) 表示第一个数组中以 \(nums_{i}\) 结尾的最长符合要求串长度。
- \(len_{i}=len_{i-1}+1, if \ \forall k \in [2,m], \ pos_{k,nums_{i-1}}+1=pos_{k,nums_{i}}\)
void solve() {
vector<vector<int>> nums(m+1,vector<int>(n+1)),pos(m+1,vector<int>(n+1));
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
nums[i][j]=read();
pos[i][nums[i][j]]=j;
}
}
vector<int64> len(n+1);
len[1]=1;
ans=1;
for(int i=2;i<=n;i++){
bool flag=true;
for(int j=2;j<=m;j++){
if(pos[j][nums[1][i]]!=pos[j][nums[1][i-1]]+1){
len[i]=1;
flag=false;
break;
}
}
if(flag){
len[i]=len[i-1]+1;
}
ans+=len[i];
}
printf("%lld",ans);
return;
}
2022.8.3 Maximum subrectangle
- Description:给出一个长度为 \(n\) 的数组 \(a\) ,和一个长度为 \(m\) 的数组 \(b\) ,这两数组用向量相乘的方式形成了一个 \(n*m\) 的矩阵, \(c_{ij}=a_{i}*b_{j}\) ,现在我们需要在矩阵中找到最大的矩阵使矩阵中所有值的和小于等于 \(x\) 。
- 经过仔细观察发现,我们所要求的 \(\sum_{i=x_{1}}^{x_{2}}\sum_{j=y_{1}}^{y_{2}}c_{ij}=(\sum_{i=x_{1}}^{x_{2}}a_{i})*(\sum_{j=y_{1}}^{y_{2}}b_{j})\)
- 于是,我们就可以先预处理每一种长度下 \(a,b\) 数组线段和的最小值,枚举所有可能长度(可以用双指针优化),找出最小面积。
void solve() {
vector<int64> a(n+1),b(m+1);
vector<int64> min_1(n+1,inf),min_2(m+1,inf);
for(int i=1;i<=n;i++){
cin>>a[i];
// a[i]=read();
}
for(int i=1;i<=m;i++){
cin>>b[i];
// b[i]=read();
}
int64 x;
cin>>x;
// int x=read();
for(int i=1;i<=n;i++){
int64 tmp=0;
for(int j=i;j<=n;j++){
tmp+=a[j];
if(min_1[j-i+1]>tmp)min_1[j-i+1]=tmp;
}
}
for(int i=1;i<=m;i++){
int64 tmp=0;
for(int j=i;j<=m;j++){
tmp+=b[j];
if(min_2[j-i+1]>tmp)min_2[j-i+1]=tmp;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(min_1[i]*min_2[j]<=x){
ans=max(ans,(int64)i*j);
}
}
}
// printf("%lld",ans);
cout<<ans;
return;
}
2022.8.4 Array division
- Description:移动一个数使数组可以划分为和相等的两段。
- 维护两个哈希表记录左右有的值及其个数,当枚举到某个数使目前的和大于 \(target\) ,就看能不能从前面找到一个值为 \(tmp-target\) 的数,如果有就把这个数移动到后面去,结束。反之亦然。
void solve() {
map<int64,int64> left,right;
vector<int64> nums(n+1);
int64 sum=0;
for(int i=1;i<=n;i++){
cin>>nums[i];
sum+=nums[i];
right[nums[i]]++;
}
if(sum&1){
cout<<"NO";
return;
}
int64 target=sum/2;
int64 tmp=0;
for(int i=1;i<=n;i++){
tmp+=nums[i];
right[nums[i]]--;
left[nums[i]]++;
if(tmp==target){
cout<<"YES";
return;
}
else if(tmp>target){
if(left[tmp-target]>0){
cout<<"YES";
return;
}
}
else{
if(right[target-tmp]>0){
cout<<"YES";
return;
}
}
}
cout<<"NO";
return;
}
2022.8.5 Maximum xor Secondary
- 由于是要求最大和次大的异或和最大,那么我们就维护一个单调栈,枚举每个元素求出该元素是次大元素的最大区间,所有求出的区间所得答案就是所有可能答案。
func solve(){
nums:=make([]int64,n)
for i:=0;i<n;i++{
Fscan(r,&nums[i])
}
ans:=int64(0)
st_1:=[]int{}
//维护一个单调递减的单调栈即可
for i:=0;i<n;i++{
for len(st_1)>0 && nums[st_1[len(st_1)-1]]<=nums[i]{
st_1=st_1[:len(st_1)-1]
}
if len(st_1)>0{
ans=max(ans,int64(nums[i])^int64(nums[st_1[len(st_1)-1]]))
}
st_1=append(st_1,i)
}
st_2:=[]int{}
for i:=n-1;i>=0;i--{
for len(st_2)>0 && nums[st_2[len(st_2)-1]]<=nums[i]{
st_2=st_2[:len(st_2)-1]
}
if len(st_2)>0{
ans=max(ans,int64(nums[i])^int64(nums[st_2[len(st_2)-1]]))
}
st_2=append(st_2,i)
}
Fprint(w,ans)
return
}
2022.8.8 Buildings a fence
- 维护一个当前遍历到的栅栏底部最小可放位置与最大可放位置的变量即可。
def solve():
n,k=mp()
base=mp()
low,high=base[0],base[0]
# low 和 high 各维护下界最小海拔和最大海拔
for i in base[1:]:
low,high=max(low-k+1,i),min(high+k-1,i+k-1)
if low>high or low>=i+k:
print("NO")
return
print("YES")if low==base[-1] else print("NO")
return
2022.8.9 George and job
- \(dp_{i,j}\) 表示在前 \(i\) 个数中选出 \(j\) 组的最大可得和。
\(dp_{i,j}=\max(dp_{i-1,j},\sum_{k=i-m}^{i}nums_{k}+dp_{i-m,j-1})\)
func solve(){
pre:=make([]int64,n+1)
nums:=make([]int64,n+1)
for i:=1;i<=n;i++{
Fscan(r,&nums[i])
pre[i]=nums[i]+pre[i-1]
}
dp:=make([][]int64,n+1)
dp[0]=make([]int64,k+1)
for i:=1;i<=n;i++{
dp[i]=make([]int64,k+1)
for j:=int64(1);j<=k;j++{
dp[i][j]=dp[i-1][j]
if i>=m{
dp[i][j]=max(dp[i][j],pre[i]-pre[i-m]+dp[i-m][j-1])
}
}
}
Fprint(w,dp[n][k])
return
}
# 附带坑爹超时记忆化
def solve():
nums=mp()
pre=[0]+list(accumulate(nums))
# @lru_cache(None)
# def dfs(pos,cnt):
# if cnt==0:return 0
# ret=0
# for i in range(pos,cnt*m-1,-1):
# ret=max(ret,pre[i]-pre[i-m]+dfs(i-m,cnt-1))
# return ret
# print(dfs(n,k))
dp=[[0]*(k+1) for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,k+1):
dp[i][j]=dp[i-1][j]
if i>=m:
dp[i][j]=max(dp[i][j],dp[i-m][j-1]+pre[i]-pre[i-m])
print(dp[-1][-1])
return
2022.8.10 Pchelyonok and Segments
- 由于本题正着写转移方程会很别扭,所以我们倒着写。
- \(dp_{i,j}\) 表示从 \([i,n)\) 中选出 \(j\) 个符合要求的区间时,第 \(k\) 个区间的最大值(贪心地想:为后面值小的区间留空间)。
\(dp_{i,j}=\max(dp_{i+1,j},\sum_{k=i}^{i+j-1})\)
void solve() {
cin >> n;
vector<int64> pre(n + 1);
vector<int64> nums(n + 1);
for (int i = 1; i <= n; i++) {
cin >> nums[i];
pre[i] = nums[i] + pre[i - 1];
}
k=((int)sqrt(1+8*n)-1)/2+1;
vector<vector<int64>> dp(n + 2, vector<int64>(k));
for (int j = 1; j < k; j++)dp[n + 1][j] = -inf;
dp[n + 1][0] = inf;
for (int i = n; i >= 1; i--) {
for (int j = 0; j < k; j++) {
dp[i][j] = dp[i + 1][j];
if(j*(j+1)/2>(n-i+1))break;
if (j and i + j - 1 <= n and pre[i + j - 1] - pre[i - 1] < dp[i + j][j - 1]) {
dp[i][j] = max(dp[i][j], pre[i + j - 1] - pre[i - 1]);
}
}
}
ans = 0;
for (int i = 0; i < k; i++) {
if (dp[1][i] > 0)ans = i;
}
cout << ans << endl;
return;
}
2022.8.11 The Number Of Good Substring
- 又是一道让人头疼的构造题,题目要求求出所有使 \(int(s,base=2)=len(s)\) 的连续子串个数。
- 我们遍历枚举每一个 \(1\) ,同时维护其到上一个 \(1\) 之间 \(0\) 的个数(即可以添加的前缀零的个数),向右拓展序列直至加前缀零也不能再满足要求。
\[j+1\leq2^{j}\leq num\leq cnt_{0}+j+1
\]
- 当满足上式的时候,一定可以补上 \(cnt_{0}+j+1-num\) 个前缀零补齐长度。
func solve(){
Fscan(r,&s)
cnt_0,ans:=0,0
for i,ch:=range s{
if ch=='0'{
cnt_0++
continue
}
num:=0
for j,ch:=range s[i:]{
num=num<<1|int(ch&1)
if num>cnt_0+j+1{
break
}
ans++
}
cnt_0=0
}
Fprintln(w,ans)
return
}
2022.8.12 AND, OR and square sum
- 参考妙妙题性质5、6,得出结论,操作不会使情况变差。
- 那么贪心地考虑,我们将所有位上的 \(1\) 放进桶里,每次遍历造出一个最大的数,也就是每次尽可能造出能造的最大数(总可以通过某位有的一构造出来)。
void solve() {
int64 ans=0;
vector<int64> cnt(21);
for(int i=0;i<n;i++){
int64 tmp;
cin>>tmp;
for(int j=0;j<20;j++){
if(tmp&(1<<j)){
cnt[j]++;
}
}
}
for(int i=0;i<n;i++){
int64 tmp=0;
for(int j=0;j<20;j++){
if(cnt[j]){
tmp|=(1<<j);
cnt[j]--;
}
}
ans+=tmp*tmp;
}
cout<<ans;
return;
}
2022.8.15 Matrix walk
- 模拟走矩阵即可,每次跳行更新 \(y\) 值。最终判否条件如下:
- 所有得出的 \(\vert root_{i}-root_{i-1}\vert\geq 1\) 的值必须相同。
- 不能出现 \(\vert root_{i}-root_{i-1}\vert\geq 1, y\neq 1\) 的换行情况。
func solve(){
root:=make([]int64,n)
Fscan(r,&root[0])
y:=int64(1)
for i:=1;i<n;i++{
Fscan(r,&root[i])
if root[i]==root[i-1]{
Fprint(w,"NO")
return
}
if abs(root[i]-root[i-1])!=1{
y=abs(root[i]-root[i-1])
}
}
for i:=1;i<n;i++{
if abs(root[i]-root[i-1])!=1 && abs(root[i]-root[i-1])!=y{
Fprint(w,"NO")
return
}else if abs(root[i]-root[i-1])==1 && y!=1 && (root[i]-1)/y!=(root[i-1]-1)/y{
Fprint(w,"NO")
return
}
}
Fprint(w,"YES\n1000000000 ",y)
return
}
2022.8.16 By Elevator or Stairs?
- 简单DP题,\(dp_{i,0|1}\) 表示从 \(i-1\) 到 \(i\) 楼乘坐电梯或者走楼梯所需最小时间。
- \(dp_{i,0}=\min(dp_{i-1,0}+a_{i-1},dp_{i-1,1}+a_{i-1})\)
- \(dp_{i,1}=\min(dp_{i-1,1}+b_{i-1},dp_{i-1,0}+b_{i-1}+c)\)
func solve(){
a,b:=make([]int64,n+1),make([]int64,n+1)
dp:=make([][]int64,n+1)
for i:=1;i<=n-1;i++{
Fscan(r,&a[i])
}
for i:=1;i<=n-1;i++{
Fscan(r,&b[i])
}
for i:=0;i<=n;i++{
dp[i]=make([]int64,2)
}
dp[1][0]=0
dp[1][1]=k
Fprint(w,0," ")
for i:=2;i<=n;i++{
dp[i][0]=min(dp[i-1][0]+a[i-1],dp[i-1][1]+a[i-1])
dp[i][1]=min(dp[i-1][1]+b[i-1],dp[i-1][0]+b[i-1]+k)
Fprint(w,min(dp[i][0],dp[i][1])," ")
}
return
}
2022.8.17 Foe Pairs
- 解法一:尺取法,将每个数对用set记录,然后滑。
- 解法二:简化滑窗,直接更新对于每一个 \(right\) ,\(left\) 指针最多延申到的地方。
def solve():
n,m=mp()
nums=mp()
ans=0
record=[0]*(n+1)
edge=[0]*(n+1)
for i in range(n):
record[nums[i]]=i
for i in range(m):
x,y=mp()
x,y=record[x],record[y]
if x>y:x,y=y,x
edge[y]=max(edge[y],x+1) #更新对于以y为区间右端点最大可以延申到的下标位置
l=0
for r in range(n):
l=max(l,edge[r])
ans+=r-l+1
print(ans)
return
2022.8.18 Playing Piano
- 解法一:DFS暴搜。
inline void dfs(int pos, int val) {
if (pos == n) {
for (int i = 0; i < n; i++)cout << record[i] << " ";
exit(0);
}
if (vis[pos][val])return;
vis[pos][val] = true;
record[pos] = val;
if (nums[pos] > nums[pos + 1]) {
for (int i = 1; i < val; i++) {
dfs(pos + 1, i);
}
}
else if (nums[pos] < nums[pos + 1]) {
for (int i = val + 1; i <= 5; i++) {
dfs(pos + 1, i);
}
}
else {
for (int i = 1; i <= 5; i++) {
if (i != val) {
dfs(pos + 1, i);
}
}
}
}
void solve() {
nums.resize(n + 1);
record.resize(n + 1);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
for (int i = 1; i <= 5; i++) {
dfs(0, i);
}
cout << -1;
return;
}
- 解法二:灵佬做法,纯纯的构造。
- 递增就尽可能从小开始。
- 递减就尽可能从最大开始。
- 相等的段元素取 \(2\) 或 \(3\) 。
def solve():
n=it()
nums=mp()
ans=[1]*(n)
if n==1:
print(1)
return
if nums[0]>nums[1]:ans[0]=5
for i in range(1,n):
if nums[i]>nums[i-1]:
if i>1 and nums[i-1]<=nums[i-2]:
ans[i-1]=1
if ans[i-1]==ans[i-2]:ans[i-1]=2
ans[i]=ans[i-1]+1
elif nums[i]<nums[i-1]:
if i>1 and nums[i-1]>=nums[i-2]:
ans[i-1]=5
if ans[i-1]==ans[i-2]:ans[i-1]=4
ans[i]=ans[i-1]-1
else:
ans[i]=(ans[i-1]-1)%3+2
if ans[i]<1 or ans[i]>5:
print(-1)
return
print(' '.join(map(str,ans)))
return
2022.8.19 Binary Numbers AND Sum
- 对齐 \(a\) 和 \(b\) 的位后计算 \(a\) 中每个 \(1\) 需要计算的次数即可。
- 记录前缀即可。
def solve(n:int,m:int,a:str,b:str):
mod=998244353
if n>m:
a=a[-m:]
else:a='0'*(m-n)+a
n=len(a)
ans=0
pre=0
for i in range(n):
if b[i]=='1':pre+=1
if a[i]=='1':ans=(ans+pow(2,n-i-1,mod)*pre)%mod
print(ans%mod)
return
2022.8.22 Vasya And The Matrix
- 首先判断能否构造:计算所有元素异或和是否为 \(0\) 就行。
- 接下来构造矩阵,将 \((n-1)*(m-1)\) 的矩阵全填 \(0\) 。剩下的根据所给数据填上去就行了。
def solve():
row_sum=mp()
col_sum=mp()
tmp=0
for num in row_sum:tmp^=num
for num in col_sum:tmp^=num
if tmp:
print("NO")
return
print("YES")
row,col=len(row_sum),len(col_sum)
for i in range(row-1):
p=[]
for j in range(col-1):
p.append(str(0))
p.append(str(row_sum[i]))
print(" ".join(p))
tmp^=row_sum[i]
ans=col_sum[col-1]^tmp
p=[]
for j in range(col-1):
p.append(str(col_sum[j]))
p.append(str(ans))
print(" ".join(p))
return
2022.8.23 Array and Segments
- 先说 easy version 的解法:枚举每一个元素作为我们的最大值,在枚举操作区间,如果区间不包含我们枚举出的最大值则执行该操作。得出答案。
void solve() {
vector<int> nums(n + 1);
vector<int> dif(n + 2);
for (int i = 1; i <= n; i++) {
cin >> nums[i];
dif[i] = nums[i] - nums[i - 1];
}
vector<pii> query(m + 1);
for (int i = 1; i <= m; i++) {
int left, right;
cin >> left >> right;
query[i] = mkp(left, right);
}
int ans = 0;
vector<int> ansRecord;
for (int i = 1; i <= n; i++) {
vector<int> tmp = dif;
vector<int> record;
for (int j = 1; j <= m; j++) {
if (i <= query[j].s_ and i >= query[j].f_) {
continue;
}
else {
tmp[query[j].f_]--, tmp[query[j].s_ + 1]++;
record.epb(j);
}
}
int minn = inf, num = 0;
for (int i = 1; i <= n; i++) {
num += tmp[i];
minn = min(minn, num);
}
if (nums[i] - minn > ans) {
ans = nums[i] - minn;
ansRecord = record;
}
}
cout << ans << endl;
cout << ansRecord.size() << endl;
for (auto idx : ansRecord) {
cout << idx << " ";
}
cout << endl;
return;
}
2022.8.24 Interesting array
- 奇妙的位运算题,判断是否符合要求的方法是枚举每一位,对每次询问的该位进行判断,如果 \(x \land (1<<j)=1\) 那么说明该区间需要每个元素的该位都是 \(1\) 。那么我们就用差分数组进行区间修改,最后再枚举每一个询问,对 \(x \land (1<<j)=0\) 的询问,判断该区间是否有 \(0\) 的存在即可。
- 判断是否有 \(0\) 的存在只需要用个前缀和就行了。
def solve(n,m):
query=[]
ans=[0]*(n+1)
for i in range(m):
query.append(mp())
def check(x)->bool:
dif=[0]*(n+2)
pre=[0]*(n+2)
for i in range(m):
l,r,q=query[i]
if x&q:
dif[l]+=1
dif[r+1]-=1
for i in range(1,n+1):
dif[i]+=dif[i-1]
pre[i]=pre[i-1]+1 if dif[i]==0 else pre[i-1]
if dif[i]:ans[i]|=x
for i in range(m):
l,r,q=query[i]
if q&x==0 and pre[r]==pre[l-1]:return False
return True
for i in range(31):
if not check(1<<i):
print("NO")
return
print("YES")
print(" ".join(map(str,ans[1:])))
return
2022.8.28 ATM and Students
- 尺取法:与普通尺取不同的是,本题由于前缀和不单调,有些答案不能更新,但是我们发现在滑动左端点过程中,由于窗口在减小所以不会更新答案。
def solve():
n,s=mp()
nums=mp()
pre=[0]+list(accumulate(nums))
r,ans=0,0
ansL,ansR=0,0
for l in range(n):
while r+1<=n and pre[r+1]-pre[l]>=-s:
r+=1
if r-l+1>ans:
ans=r-l+1
ansL,ansR=l+1,r
if rDomino==l:r+=1
if ansR==0:
print(-1)
return
print(ansL,ansR)
return
2022.8.29 Domino
- 分情况讨论,见代码。
func solve(){
Fscan(r,&n,&m,&k)
if n%2==0 && m%2==0{
if k%2==0{
Fprint(w,"YES\n")
}else{
Fprint(w,"NO\n")
}
}else if n%2==1 && m%2==0{
if (k-m/2)%2==0 && k>=m/2{
Fprint(w,"YES\n")
}else{
Fprint(w,"NO\n")
}
}else if n%2==0 && m%2==1{
if k%2==0 && k<=(m-1)*n/2{
Fprint(w,"YES\n")
}else{
Fprint(w,"NO\n")
}
}else{
Fprint(w,"NO\n")
}
return
}
2022.8.30 Erase and Extend
- 首先本题有个性质:先进行删除操作再复制相比先复制再删除一定使答案不劣(可以感性的考虑一下,先复制相当于将大的字符用了上去,那么先删除一定更优)。
- 这样这题的简单版就可以做出来了:枚举所有前缀进行复制再比较得出最小字典答案。
- 但是本题还可以有线性做法:枚举前缀结尾更新最优前缀。证明
def solve():
n,k=mp()
s=input().strip()
ptr=1
for i in range(n):
if s[i]>s[i%ptr]:break
elif s[i]<s[i%ptr]:ptr=i+1
ans=s[:ptr]*(k//ptr+1)
print(ans[:k])
return
2022.8.31 A Twisty Movement
- 这题真不会,解法是:最终答案一定是一个形如 \([1\dots1][2\dots2][1\dots1][2\dots2]\) 的序列。
- 定义 \(dp_{i,j}\) 表示前 \(i\) 个元素 组成前 \(j\) 段的答案数。
\[dp_{i,1}=dp_{i-1,1}+(nums_{i}==1)\\
dp_{i,2}=\max(dp_{i-1,1},dp_{i,2}+(nums_{i}==2))\\
dp_{i,3}=\max(dp_{i-1,2},dp_{i,3}+(nums_{i}==1))\\
dp_{i,4}=\max(dp_{i-1,3},dp_{i,4}+(nums_{i}==2))
\]
func solve(){
nums:=make([]int,n+1)
for i:=1;i<=n;i++{
Fscan(r,&nums[i])
}
dp:=make([]int,5)
for i:=1;i<=n;i++{
if nums[i]==1{
dp[1]++
dp[2]=max(dp[1],dp[2])
dp[3]=max(dp[2],dp[3]+1)
dp[4]=max(dp[3],dp[4])
}else{
dp[2]=max(dp[1],dp[2]+1)
dp[3]=max(dp[2],dp[3])
dp[4]=max(dp[3],dp[4]+1)
}
}
Fprint(w,dp[4])
return
}
Even-Odd xor
- 奇妙的题目:由于要让奇数位和偶数位的异或和相等,我们可以让整个数组的异或和为 \(0\) 。
- 这样我们只需要留出三个空位就行了:
- \(num_{n-2}=1<<29,num_{n-1}=1<<30,num_{n}=sum\bigoplus num_{n-2}\bigoplus num_{n-1}\) ,这样就可以用 \(num_{n}\) 消除所有奇数项,留下偶数项。(太妙了!)
def solve():
n=it()
s,ans=0,[]
for i in range(1,n-2):
ans.append(i)
s^=i
ans.append(1<<29)
ans.append(1<<30)
ans.append(s^ans[-1]^ans[-2])
print(" ".join(map(str,ans)))
return
2022.9.1 Number of Permutation
- 挺有意思的排列组合题:\(ans=n!-\vert A\vert-\vert B\vert+\vert A\cap B\vert\)
- \(\vert A\vert\) 和 \(\vert B\vert\) 很好算,\(\vert A\vert=\prod_{i=1}^{n}cnt_{i}!\),单纯的将相同元素计算全排列相乘即可。
- \(\vert A\cap B\vert\) 的计算:我们先将数组按照索引顺序 \(0,1\) 排序,遍历数组,一旦出现 \(b_{i-1}>b_{i}\) ,那么就不存在同时满足 \(A,B\) 单调不降的答案。反之,向上面一样计算 \((A_{i},B_{i})\) 的排列即可。
def solve():
n=it()
mod=998244353
nums,cnt1,cnt2,c=[],Counter(),Counter(),Counter()
f=[0]*(n+1)
f[0]=1
for i in range(1,n+1):
x,y=mp()
nums.append((x,y))
c[nums[-1]]+=1
cnt1[x]+=1
cnt2[y]+=1
f[i]=f[i-1]*i%mod
ans,s=f[n],1
for val in cnt1.values():
s=s*f[val]%mod
ans=(ans-s)%mod
s=1
for val in cnt2.values():
s=s*f[val]%mod
ans=(ans-s)%mod
nums.sort()
cnt,s=0,1
for i in range(n-1):
if nums[i][1]>nums[i+1][1]:
s=0
break
for val in c.values():
s=s*f[val]%mod
ans=(ans+s)%mod
print(ans)
return
2022.9.2 Increasing by Modulo
- 又是苦涩的茶。我们发现最多也就会需要操作 \(m\) 次,即将所有元素变为 \(0\) 。这样我们可以用二分来得出答案。考虑
check
函数:- 当 \(num_{i-1}<num_{i}\) ,我们可以尽可能将 \(num_{i}\) 变得与 \(num_{i-1}\) 接近。即当 \(num_{i-1}+m-num_{i}\leq x\) 时,我们可以将 \(num_{i}\) 变成 \(num_{i-1}\) 。
- 当 \(num_{i-1}>num_{i}\) ,如果 \(num_{i-1}-num_{i}>x\) ,此时我们无论如何也不能将数组变为符合条件的情况。
- 当 \(num_{i-1}=num_{i}\) ,什么也不做。
def solve():
n,m=mp()
nums=[0]+mp()
def check(x):
tmp=[i for i in nums]
for i in range(1,n+1):
if tmp[i-1]>tmp[i]:
if tmp[i-1]-tmp[i]>x:
return False
tmp[i]=tmp[i-1]
else:
if tmp[i-1]-tmp[i]+m<=x:
tmp[i]=tmp[i-1]
return True
l,r=0,m
while l<r:
mid=(l+r)>>1
if check(mid):
r=mid
else:
l=mid+1
print(l)
return
2022.9.5 Bits
- 巧妙的贪心:从左边界开始枚举,给数字填上 \(0\) ,直到数字会越过右边界为止。
def solve():
l,r=mp()
i=1
while (l|i)<=r:
l|=i
i<<=1
print(l)
return
2022.8.7 Sea Battle
- 高一玩的游戏在这题派上了用场,每隔 \(b\) 个长度打一发就行。
def solve():
n,a,b,k=mp()
s=input().strip()
tmp,ans=0,[]
for i in range(n):
if s[i]=='1':
tmp=0
else:
tmp+=1
if tmp==b:
ans.append(i+1)
tmp=0
ret=len(ans)-(a-1)
print(ret)
print(" ".join(map(str,ans[:ret])))
return
2022.9.7 Vessels
- 并查集:将溢出的沙漏合并到下一层的祖先中去。
def solve():
n=it()
nums=mp()
nums.append(math.inf)
q=it()
father=[i for i in range(n+1)]
ans=[0]*(n+1)
def find(x):
while x!=father[x]:
father[x]=father[father[x]]
x=father[x]
return x
for i in range(q):
get=mp()
if len(get)==2:
print(ans[get[1]-1])
else:
start,x=get[1]-1,get[2]
ans[start]+=x
while start<n and ans[start]>nums[start]:
father[find(start)]=find(start+1)
to=find(start+1)
ans[to]+=ans[start]-nums[start]
ans[start]=nums[start]
start=to
return
2022.9.8 Nephren gives a riddle
- 最显然的做法是递归,不过py被CF制裁了,记忆化会爆栈(不过c++貌似还是能过的)。
- 只能用while优化。
func solve(){
T,n,k:=0,0,int64(0)
f:=[]int64{75}
for f[len(f)-1]<1e18{
f=append(f,f[len(f)-1]*2+68)
}
dfs:=func()byte{
k--
for{
if n<len(f) && k>=f[n]{
return '.'
}
if n==0{
return `What are you doing at the end of the world? Are you busy? Will you save us?`[k]
}
if k<34{
return `What are you doing while sending "`[k]
}
k-=34
n--
if n>=len(f) || k<f[n]{continue}
k-=f[n]
if k<32{return`"? Are you busy? Will you send "`[k]}
k-=32
if k<f[n]{continue}
return `"?`[k-f[n]]
}
}
Fscan(r,&T)
for ;T>0;T--{
Fscan(r,&n,&k)
Fprint(w,string(dfs()))
}
return
}
2022.9.9 Zigzags
- 枚举 \(j,k\) 才能保证计算的 \(i,l\) 的相对位置。用前缀和统计所有元素出现频次即可。
def solve():
n=it()
nums=[0]+mp()
pre=[[0]*(3005) for _ in range(n+2)]
for i in range(1,n+1):
for j in range(3005):
pre[i][j]=pre[i-1][j]
pre[i][nums[i]]+=1
ans=0
for j in range(2,n+1):
for k in range(j+1,n):
ans+=(pre[j-1][nums[k]])*(pre[n][nums[j]]-pre[k][nums[j]])
print(ans)
return
2022.9.9 加餐:Tokitsukaze and Strange Inequality
- 双指针+预处理:
func solve(){
Fscan(r, &n)
a := make([]int, n)
l := make([][]int, n)
for i := range a {
Fscan(r, &a[i])
l[i] = make([]int, n+1)
s:=0
for j, v := range a[:i] {
if v < a[i] {s++}
l[i][j+1] = s
}
}
ans := int64(0)
for i := 1; i < n-2; i++ {
for j, c := n-2, 0; j > i; j-- {
if a[i] > a[j+1] {
c++
}
ans += int64(l[j][i] * c)
}
}
Fprintln(w, ans)
return
}
2022.9.16 High Load
- 这种输出一棵树的题目我是真的不会搞输出。。。
- 想法很简单,按倍数扩散出 \(k\) 条链出来,如果有余数则直接每条链加上一个点就行。
def solve():
n,k=mp()
dep,left=divmod(n-1,k)
ans=dep*2+(2 if left>=2 else 1 if left>=1 else 0)
print(ans)
node=1
for i in range(k):
root=0
for j in range(dep+1 if i<left else dep):
print(f"{root+1} {node+1}")
root=node
node+=1
return
2022.9.22 Case of Fagutive
- 从小到大枚举所有的桥,固定桥的时候,将所有最小间隔小于桥长的间隔放入小根堆中,在看堆顶间隔的最大长度是否小于桥长。
- 如果是,那么之后的桥无论如何都没法覆盖这个间隔了,就直接返回NO。
- 反之用贪心的思想弹出堆顶最小的间隔,这样可以保证之后的间隔尽可能的大,方便覆盖。
- 最后判断是否覆盖了所有的间隔。
def solve():
n,m=mp()
lr,ran=[],[]
for i in range(n):
l,r=mp()
if i>0:ran.append((l-lr[-1][1],r-lr[-1][0],i))
lr.append((l,r))
bridge=mp()
for i in range(m):
bridge[i]=(bridge[i],i)
bridge.sort()
ran.sort()
pq=[]
ans=[0]*n
j=0
for length,i in bridge: # 枚举所有每个桥
while j<n-1 and length>=ran[j][0]: # 放入每一个满足间隔条件的岛间距
left,right,idx=ran[j]
heappush(pq,(right,idx)) # 放入符合要求的间隔,按最大可接受长度从小到大排
j+=1
if pq and pq[0][0]<length:
return print("No")
if not pq:
continue
_,idx=heappop(pq)
ans[idx]=i+1
if j<n-1:
return print("No")
print("Yes")
print(" ".join(map(str,ans[1:])))
return
补题:同样是区间最小覆盖问题
- 这题就相对上一题要容易不少,本题直接给出了所有的区间,这样我们只需要从小到大枚举防晒霜的值,每次将最小区间去除即可。
def solve():
c,l=mp()
lr=[]
for _ in range(c):
minn,maxn=mp()
lr.append((minn,maxn))
cream=[]
for _ in range(l):
val,num=mp()
cream.append((val,num))
lr.sort()
cream.sort()
ans,pq=c,[]
j=0
for val,num in cream:
while j<c and val>=lr[j][0]:
heappush(pq,lr[j][1])
j+=1
while pq and pq[0]<val:
heappop(pq)
ans-=1
while pq and num>0:
heappop(pq)
num-=1
ans-=len(pq)
print(ans)
return
力扣里同类的题目
- 茶的延拓——区间最小覆盖问题:枚举所有排序后的
query
,固定query
将所有满足左端点小于该值的区间放入SortedList
中,序列按间隔大小从小到大排,同间隔情况按右端点大小排列。 - 接下来将所有右端点小于当前
query
的区间从列表中删去。 - 最后更新答案为序列头。
代码
from sortedcontainers import SortedList
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
queries=sorted((queries[i],i) for i in range(len(queries)))
intervals.sort()
j,n=0,len(intervals)
ans=[-1]*(len(queries))
s=SortedList()
for length,idx in queries:
while j<n and intervals[j][0]<=length:
l,r=intervals[j]
s.add((r-l+1,r))
j+=1
while s and s[0][1]<length:
s.pop(0)
if s:
ans[idx]=s[0][0]
return ans
2022.9.28 非常好的ODT练习题
- 对区间修改操作进行标号,同时对被修改区间整体推平,赋值为操作的标号。
- 在对 \(pos\) 进行访问时,我们只需要先查询该位置被哪一次操作最后覆盖就行了。
void solve() {
vector<int> a(n + 1), b(n + 1);
chtholly_tree tree;
tree.insert(1, n + 1, 0);
int cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
vector<tuple<int, int, int>> q;
q.epb(make_tuple(0,0,0));
while (m--) {
int order;
cin >> order;
if (order == 1) {
int x, y, k;
cin >> x >> y >> k;
cnt++;
q.epb(make_tuple(x,y,k));
tree.assign(y, y + k - 1, cnt);
}
else {
int pos;
cin >> pos;
int ans = tree.query(pos, pos);
if (!ans) {
cout << b[pos] << "\n";
}
else {
int x = get<0>(q[ans]), y = get<1>(q[ans]), k = get<2>(q[ans]);
cout << a[pos - y + x] << "\n";
}
}
}
return;
}
2022.9.29 Amr and Chemistry
- 这道题能和换根DP联系起来真是太神奇了。
- 我们构建一个以 \(1\) 为根,每个节点的左右儿子为 \(2x,2x+1\) 。这样我们发现最优答案可能就是所有节点的LCA,然后我们只往左儿子走(为什么不往右儿子走呢?因为左子树上的点可能到不了右子树上的点,毕竟操作不能凭空变出1来)。这样我们就可以用换根DP解决了。
void dfs(int root) {
if (root > M)return;
dep[root << 1] = dep[root << 1 | 1] = dep[root] + 1;
dfs(root << 1), dfs(root << 1 | 1);
sz[root] = sz[root << 1] + sz[root << 1 | 1] + isNode[root];
return;
}
void solve() {
vector<int> nums(n);
for (int i = 0; i < n; i++)cin >> nums[i], isNode[nums[i]]++;
int root = M;
dfs(1);
for (; root >= 1; root--) {
if (sz[root] == n)break;
}
ans = Linf;
int64 sum = 0;
for (int num : nums) {
sum += dep[num] - dep[root];
}
ans = min(ans, sum);
while ((root << 1) <= M) {
sum += n - 2 * sz[root << 1];
root <<= 1;
ans = min(ans, sum);
}
cout << ans;
return;
}
2022.10.5 Vladik and Memorable Trip
- 有一说一,区间DP真难。
- 这题有两个点很难想:
- \(dp_{i}=\max(dp_{i},dp_{j}+xor)\) ,其中 \(xor\) 是每一个包含所有区间内有的元素的区间异或和。
- 判断区间合法性:预处理
left
数组和right
数组,记录每个元素最远左右端点。
def solve():
n=it()
nums=[0]+mp()
l,r=defaultdict(int),defaultdict(int)
for i,num in enumerate(nums):
if not l[num]:l[num]=i
r[num]=i
dp=[0]*(n+1)
for i in range(1,n+1): # 枚举右端点
dp[i]=dp[i-1]
left,xor=l[nums[i]],0
for j in range(i,0,-1):
tmp=nums[j]
if r[tmp]>i:break
left=min(left,l[tmp])
if l[tmp]==j: # 左侧没有该元素了
xor^=tmp
if left>=j:
dp[i]=max(dp[i],dp[j-1]+xor)
print(dp[n])
return
2022.11.14 Common Sequence
- 基本 dp 题:
\[dp_{i,j}=
\begin{cases}
dp_{i-1,j}+dp_{i,j-1}-dp_{i-1,j-1}\text{, }numsOne_{i}\neq numsTwo_{j} \\
dp_{i-1,j}+dp_{i,j-1}+1\text{, }numsOne_{i}=numsTwo_{j}
\end{cases}
\]
def solve():
n,m=mp()
nums1=[0]+mp()
nums2=[0]+mp()
dp=[[0]*(n+1) for _ in range(m+1)]
for i in range(1,m+1):
for j in range(1,n+1):
dp[i][j]=(dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+(dp[i-1][j-1]+1 if nums1[j]==nums2[i] else 0))%MOD
print((dp[m][n]+1)%MOD)
return
2022.11.6 Pond
- 本题需要找出所有子矩阵中的最小中位数。
- 我们可以用二分查找,找出最小的可以使之成为至少一个子矩阵中的中位数的值。
- 对于本题的 check 函数优化我们可以使用 \(01\) 二维前缀和统计矩阵中每个元素与当前值的大小关系。
def solve():
n,k=mp()
grid=[[0]*(n+1) for _ in range(n+1)]
for i in range(1,n+1):
grid[i]=[0]+mp()
l,r=0,INF
presum=[[0]*(n+1) for _ in range(n+1)]
def check(x):
for i in range(1,n+1):
for j in range(1,n+1):
presum[i][j]=presum[i-1][j]+presum[i][j-1]-presum[i-1][j-1]+int(grid[i][j]<=x)
for i in range(k,n+1):
for j in range(k,n+1):
if presum[i][j]-presum[i-k][j]-presum[i][j-k]+presum[i-k][j-k]>=(k*k+1)//2:return True
return False
# 如果存在一个子矩阵至少有(k*k+1)/2个数比当前数小
while l<r:
mid=(l+r)>>1
if check(mid):
r=mid
else:
l=mid+1
print(l)
return