CF 口胡筆記 2000Ct輯

DZhearMins發表於2024-10-17
¿ 如何 搞笑 高效做題 ?

只需要口胡CF題就行啦!(

從今天起口胡 CF 按照洛谷透過人數排序的題單

CF2000 Part 1 開始


CF24E XOR on Segment

  • 給定 \(n\) 個數的序列 \(a\)\(m\) 次操作,操作有兩種:
    1. \(\displaystyle\sum_{i=l}^r a_i\)
    2. \(a_l\sim a_r\) 異或上 \(x\)
  • \(1\le n\le 10^5\)\(1\le m\le 5\times 10^4\)\(0\le a_i\le 10^6\)\(1\le x\le 10^6\)

思路:開 \(\log(A)\) 個線段樹記錄二進位制下每一位是 \(0\) 還是 \(1\)\(A\) 代表值域。空間 \(T(2n\log(A))\) ,時間 \(T(n\log(n)\log(A))\)


CF1200E Compress Words

  • Amugae 有 \(n\) 個單詞,他想把這個 \(n\) 個單詞變成一個句子,具體來說就是從左到右依次把兩個單詞合併成一個單詞。合併兩個單詞的時候,要找到最大的 \(i(i\ge 0)\),滿足第一個單詞的長度為 \(i\) 的字尾和第二個單詞長度為 \(i\) 的字首相等,然後把第二個單詞第 \(i\) 位以後的部分接到第一個單詞後面。輸出最後那個單詞。

不就是 kmp 裸題嗎,過。

等等,我看題解好像有詐。我寫一寫程式碼

廢了,寫了四遍還沒過

突然看到題解的一句話:

首先,這題要先注意題面。如果你把合併單詞的過程理解成“單詞與單詞合併”,你就會被 test 4 卡掉。【調了很久才發現】

正確的理解方式是:“單詞與合併完成的句子合併”

哦哦哦哦哦哦原來是這樣我

那複雜度不就變成 \(O(n^2)\) 了嗎?寄。

哦,原來新串和原串 getnxt 的時候只用加入與新串長度相等的原串就行了,複雜度 \(T(2n)\)

展開程式碼

#include<bits/stdc++.h>
using namespace std;
#define ff(i,l,r) for(auto i=(l);(i)<=(r);++i)
#define fi(l,r) ff(i,l,r)
#define lowbit(x) ((x)&(-(x)))
#define ll long long
#define ul unsigned ll
#define ui unsigned int
#define P 998244353
#define N 2300005
#define E 1000005
char a[N];
int nxt[N],l,r;
int getnxt(){
	nxt[l]=nxt[l+1]=l;
	fi(l+2,r){
		int j=nxt[i-1];
		while(j!=l&&a[j+1]!=a[i])j=nxt[j];
		if(a[j+1]==a[i])++j;
		nxt[i]=j;
	}
	return nxt[r]-l;
}
int n,s[N],bi;
char c_c[N],*c[N],b[N];
bool islegal(char ch){
	return (ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')||(ch>='0'&&ch<='9');
}
int main(){
	scanf("%d",&n);
	char ch=getchar();
	c[1]=c_c;
	fi(1,n){
		while(!islegal(ch))ch=getchar();
		while(islegal(ch)){
			c[i][++s[i]]=ch;
			ch=getchar();
		}
		c[i+1]=c[i]+s[i]+1;
	}
	fi(2,n){
		ff(j,1,s[i-1])b[++bi]=c[i-1][j];
		r=E;
		l=E-s[i]-1;
		ff(j,1,s[i])a[l+j]=c[i][j];
		ff(j,1,min(bi,s[i]))a[++r]=b[bi-min(bi,s[i])+j];
		int samelen=getnxt();
		ff(j,1,samelen)--bi;
	}
	fi(1,s[n])b[++bi]=c[n][i];
	fi(1,bi)putchar(b[i]);
	return 0;
}


CF784E Twisted Circuit

  • 題目描述電路圖圖片
  • 讀入四個整數 \(0\) 或者 \(1\),作為如圖所示的電路圖的輸入。請輸出按照電路圖運算後的結果。

簡單題,過。

CF235B Let's Play Osu!

  • 一個 \(01\) 串,第 \(i\) 個字元有 \(p_i\) 的機率成為 \(0\)\(1-p_i\) 的機率成為 \(1\) ,求所有(【連續 \(0\) 的長度】的平方)之和的期望。 \(n \le 10^5\)

定義 \(f_i\) 代表第 \(i\) 位是 \(1\) 的時候已知的期望。那麼有

\[\begin{align} f_i&=\sum f_{j\in[0,i-1]}\cdot(i-j-1)^2\cdot\prod p_{k\in[j+1,i-1]}\cdot(1-p_i)\\ &=\sum\limits_{j=0}^{i-1}\prod\limits_{k=j+1}^{i-1} p_k\cdot(f_j\cdot j^2-2j\cdot f_j \cdot (i-1)+f_j \cdot (i-1)^2) \end{align} \]

只要記錄 \(\displaystyle\sum f_j\cdot j^2\)\(\displaystyle\sum 2j\cdot f_j\)\(\displaystyle\sum f_j\) ,每次乘上對應的係數就能 \(O(n)\) 轉移了。

時間複雜度 \(O(n)\)

答案還有更奇妙的做法,我學習一下:

記錄 \(k_i\) 代表 \(i\) 位置的期望連續 \(0\) 長度, \(f_i\) 代表 \(i\) 位置及以前的期望。

\(k_i=(k_{i-1}+1)\times p_i\)

\(f_i = f_{i01} \times (1-p_i) + (f_{i-1}+2 \times k_{i-1}+1)\times p_i\)


CF2B The least round way

  • 上古題
  • 以左上角為起點。
  • 每次只能向右或向下走。
  • 以右下角為終點。
  • 如果我們把沿路遇到的數進行相乘,積應當以儘可能少的 \(0\) 結尾。

顯然要麼最小化 \(2\) 的數量,要麼最小化 \(5\) 的數量。

記錄 \(f^2_{x,y}\)\(f^5_{x,y}\) ,答案就是 \(\min(f^2_{m,n},f^5_{m,n})\)。為什麼處理 \(f^2\) 不用統計 \(5\) 的數量? 因為如果 \(5\)\(2\) 要少,那麼 \(f^5\) 肯定會更優。所以 \(f^2\) 能成為答案就證明 \(2\) 不比 \(5\) 多。

做完……了嗎?

注意,矩陣裡面有 \(0\)

如果 \(\min(f^2,f^5) > 1\) ,就還要考慮經過 \(0\) 的情況。 而且 \(f^2\)\(f^5\) 的統計也要避開 \(0\)


CF920F SUM and REPLACE

  • 給定 \(n\) 個數的陣列 \(a\)\(m\) 次操作。操作有兩種:
    1. \(i\in[l,r]\) 中的所有 \(a_i\) 替換為 \(d(a_i)\)\(d(x)\) 表示 \(x\) 的正約數的個數。
    2. \(\displaystyle\sum_{i=l}^r a_i\)
  • \(1\le n,m\le 3\times 10^5\)\(1\le a_i\le 10^6\)

感覺一個數操作不了幾次就會變 \(0\)

所以對每個不是 \(1\) 的數暴力修改。查詢不是 \(1\) 的數可以用線段樹 \(O(n\log n)\),可以用跳錶 \(O(n+\log n)\)更好的做法是並查集,每個 \(1\) 的 fa 指向下一個未被修改的數。 複雜度 \(O(n\alpha)\)

如何求正因數個數? \(O(A\log A)\) 向後更新預處理即可。


CF171E MYSTERIOUS LANGUAGE

    You are given a mysterious language (codenamed "Secret") available in "Custom Test" tab. Find out what this language is and write a program which outputs its name. Note that the program must be written in this language.
    This program has only one test, and it's empty (it doesn't give your program anything to read).
    Output the name of the mysterious language.

建議評黑!!!


CF383C Propagating tree

  • 很久以前,有一棵神橡樹(oak),上面有\(n\)個節點,從\(1\)~\(n\)編號,由\(n-1\)條邊相連。它的根是\(1\)號節點。

  • 這棵橡樹每個點都有一個權值,你需要完成這兩種操作:

  • \(1\) \(u\) \(val\) 表示給\(u\)節點的權值增加\(val\)

  • \(2\) \(u\) 表示查詢\(u\)節點的權值

  • 但是這不是普通的橡樹,它是神橡樹。

  • 所以它還有個神奇的性質:當某個節點的權值增加\(val\)時,它的子節點權值都增加\(-val\),它子節點的子節點權值增加\(-(-val)\)...... 如此一直進行到樹的底部。

給奇數深度和偶數深度,分別重鏈剖分、建線段樹。每個詢問就向上統計 \(val\) 的和。

複雜度 \(O(m\log n)\)

oh,原來還可以用樹狀陣列做啊。 具體做法略了。


CF1132F Clear the String

  • 給你一個串\(s\),每次可以花費 \(1\) 的代價刪去一個子串,要求子串的每一位為同一個字元。

  • 求刪去整個串的最小代價。

  • \(1\le |s|\le 500\)

  • 例如,abaca 就要刪 \(3\) 次。

\(f_{i,c}\) 代表把 \([1,i]\) 變成只有 \(c\) 字元的代價。

\(f_{i,c}=f_{i-1,c'}+[c\not=c']+[c\not=s[i]]\)

這複雜度\(O(nZ^2)\)顯然不對啊,我寫一個對拍看看為啥。

Oh,這樣就失去合併的順序,只能正序合併。

那就設 \(f[l,r,0/1] \leftarrow f[l+1,r,0/1],f[l,r-1,0/1]\) ,其中 \(0/1\) 代表這個區間最後是 \(s[l]\) 還是 \(s[r]\) 的字元。

複雜度 \(O(n^2)\) ,感覺還是不太對,但是過樣例了,寫個對拍試試。

還是寄了。原來一個區間不止能變成兩端的字元,也可以被統一成中間的字元。比如這組資料 abacbcc ,其中的 abacb 要被統一成 c 才行。

然後 TLE 了。突然我想到 \(f[l,r,c]\) ,不同 \(c\)\(f\) 相差最多隻有 \(1\) ,這啟發我們記錄最小的 \(c\) 是哪些。

展開程式碼

#include<bits/stdc++.h>
using namespace std;
#define ff(i,l,r) for(auto i=(l);(i)<=(r);++i)
#define fi(l,r) ff(i,l,r)
#define lowbit(x) ((x)&(-(x)))
#define ll long long
#define ul unsigned ll
#define ui unsigned int
#define P 998244353
#define N 505
int n,f[N][N];
char mc[N][N],a[N];
bitset<26>islowest[N][N];
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	fi(1,n)islowest[i][i][a[i]-'a']=1;
	ff(len,2,n){
		ff(l,1,n){
			int r=l+len-1;
			if(r>n)break;
			f[l][r]=0x3f3f3f3f;
			ff(j,l,r-1){
				bitset<26>tmp=islowest[l][j]&islowest[j+1][r];
				if(tmp.count()==0){
					if(f[l][j]+f[j+1][r]+1<f[l][r]){
						islowest[l][r]=islowest[l][j]|islowest[j+1][r];
						f[l][r]=f[l][j]+f[j+1][r]+1;
					}else if(f[l][j]+f[j+1][r]+1==f[l][r]){
						islowest[l][r]|=islowest[l][j]|islowest[j+1][r];
					}
				}else{
					if(f[l][j]+f[j+1][r]<f[l][r]){
						islowest[l][r]=islowest[l][j]&islowest[j+1][r];
						f[l][r]=f[l][j]+f[j+1][r];
					}else if(f[l][j]+f[j+1][r]==f[l][r]){
						islowest[l][r]|=islowest[l][j]&islowest[j+1][r];
					}
				}
			}
		}
	}
	printf("%d\n",f[1][n]+1);
	return 0;
}

然後就 AC 了。看看題解吧。

哦,原來我最開始的想法是對的。一個區間一定可以變成最左端的字元而最優。之前沒 AC 是因為一個區間 \([l,r]\) 不一定由 \([l,l]+[l+1,r]\)\([l,r-1]+[r,r]\) 合併而來。而是由 \([l,j]+[j+1,r]\) 合併而來。

展開程式碼

#include<bits/stdc++.h>
using namespace std;
#define ff(i,l,r) for(auto i=(l);(i)<=(r);++i)
#define fi(l,r) ff(i,l,r)
#define lowbit(x) ((x)&(-(x)))
#define ll long long
#define ul unsigned ll
#define ui unsigned int
#define P 998244353
#define N 505
int n,f[N][N];
char a[N];
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	ff(len,1,n){
		ff(l,1,n+1-len){
			int r=l+len-1;
			f[l][r]=(len==1)?0:0x3f3f3f3f;
			ff(j,l,r-1)f[l][r]=min(f[l][r],f[l][j]+f[j+1][r]+(a[l]!=a[j+1]));
		}
	}
	printf("%d\n",f[1][n]+1);
	return 0;
}

CF895C Square Subsets

  • 對於一些陣列 \(a\),Petya 需要找到從中間選擇非空子集,使所有數的乘積為完全平方數的方法的數量。如果這些方法所選擇的元素的索引不同,則認為這兩種是不同的方法。 因為結果可能很大,結果需要 \(\bmod ~ 10^9+7\)
  • \(1 \le a_i \le 70\)

觀察到值域 \(A\) 很小,考慮開個桶存所有 \(a_i\) 。同時發現只有 \(19\) 個質數,所以記錄 \(f[i][...19維]\) 儲存在 \(i\) 數值之前的每一個質因子的奇偶性。

對於每一個桶值 \(i\), 更新 \(f\) ,答案就是 \(\displaystyle\sum f[i][000...000]\)

為什麼題目標籤裡面有線性基?不明白,那就寫一寫程式碼吧。

展開程式碼

#include<bits/stdc++.h>
using namespace std;
#define ff(i,l,r) for(auto i=(l);(i)<=(r);++i)
#define fi(l,r) ff(i,l,r)
#define lowbit(x) ((x)&(-(x)))
#define ll long long
#define ul unsigned ll
#define ui unsigned int
#define P 1000000007
#define N 1000005
#define A 75
int ton[A];
const int prime[20]={1,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67};
const int pcnt=19;
int mask,n,erdencifang[N];
int f[A][1<<pcnt];
void reversebit(int &x,int i){
	x^=(1<<i);
}
int main(){
	scanf("%d",&n);
	erdencifang[0]=1;
	fi(1,n){
		int x;
		scanf("%d",&x);
		++ton[x];
		erdencifang[i]=(erdencifang[i-1]<<1)%P;
	}
	f[0][0]=1;
	fi(1,70){
		if(ton[i]==0){
			ff(S,0,(1<<pcnt)-1)f[i][S]=f[i-1][S];
		}else{
			int x=i;
			mask=0;
			fi(1,pcnt){
				while(x%prime[i]==0){
					reversebit(mask,i-1);
					x/=prime[i];
				}
			}
			ff(S,0,(1<<pcnt)-1){
				f[i][S^mask]=(f[i][S^mask]+(ll)f[i-1][S]*erdencifang[ton[i]-1])%P;//選奇數個
				f[i][S]=(f[i][S]+(ll)f[i-1][S]*erdencifang[ton[i]-1])%P;//選偶數個
			}
		}
	}
	printf("%d\n",(f[70][0]-1+P)%P);
	return 0;
}

就這麼水靈靈地過啦??!

為什麼要線性基(不解)

看了看線性基的做法,太妙了。是先欽定 \(cnt\) 個元素構成完全平方數的基底,剩下的數就可以隨便選了,答案是 \(2^{n-cnt}-1\)

相關文章