L.最小括號串
#數組操作 #貪心
題目
思路
感謝Leratiomyces大佬賽時的提示,否則估計還一直簽不了到()
首先,貪心地構造出最優情況:數組左半部分全是(
,右半部分全是)
,隨后通過判斷給定的區間中是否包含(
來決定是否調整當前序列。
對給定的區間按照左端點降序排序,這樣就可以從右往左有序遍歷所有區間。
設置兩個指針idl,idrid_{l},id_{r}idl?,idr?:
- idlid_{l}idl?指向未被調換的
(
中最右的(
的下標 - idrid_{r}idr?指向經過調換后的
(
中最左的(
的下標
從右往左有序遍歷所有區間:
- 如果當前區間右端點rrr在idrid_{r}idr?左側,那么說明當前區間[l,r][l,r][l,r]中一定沒有
(
,因此需要調度一個(
前往lll位置(盡量保證字典序小),即swap(ans[idl],ans[l])swap(ans[\ id_{l}\ ],ans[\ l\ ])swap(ans[?idl??],ans[?l?]) - 調度前需要注意idlid_{l}idl?是否為0,即序列左端是否還剩余
(
。如果idl==0id_{l}==0idl?==0而仍然需要調度(
,那么必然是不合法的,輸出-1
最后不要忘了遍歷整個序列判斷是否會存在)(
的非法情況
代碼實現
#include<iostream>
#include<vector>
#include<algorithm>
#include<map>
#include<unordered_set>
#include<queue>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i++)
#define per(i, a, b) for(ll i = (a); i >= (b); i--)
#define mid ((l+r)>>1)
#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
const int N=1e5+5;
int n,m;
struct lr{int l,r;bool operator<(const lr&t)const{return l>t.l;}
};
void solve() {cin>>n>>m;n*=2;vector<lr>b(m+1); vector<char>ans(n+1);rep(i,1,m){int l,r;cin>>l>>r;b[i]={l,r};}rep(i,1,n){if(i<=n/2)ans[i]='(';else ans[i]=')';}sort(b.begin()+1,b.begin()+1+m);int idl=n/2,idr=n+1;rep(i,1,m){int l=b[i].l,r=b[i].r;if(r<idr){if(idl<1){cout<<-1<<'\n';return;}swap(ans[idl],ans[l]);idr=l;idl--;}}int cntl=0,cntr=0;rep(i,1,n){if(ans[i]=='(')cntl++;else cntr++;if(cntr>cntl){cout<<-1<<'\n';return;}}rep(i,1,n)cout<<ans[i];cout<<'\n';
}int main() {ios::sync_with_stdio(0);cin.tie(0), cout.tie(0);int t = 1;cin >> t;while (t--) {solve();}return 0;
}
C.棧
#數學 #組合數 #斯特林數
題目
思路
提供一種純數學的方法:
設SnmS_{n}^mSnm?為長度為nnn的所有排列中sizesizesize為mmm的數量
易知∑i=1nSni=n!\sum_{i=1}^nS_{n}^i=n!∑i=1n?Sni?=n!,即排列數AnnA_{n}^nAnn?
接下來可以找出他的遞推:
- 當數字nnn不位于排列的最后一位時,stackstackstack的poppoppop功能必定會將其poppoppop掉,即數字nnn不產生貢獻。數字nnn不位于最后一位的可能有n?1n-1n?1種,那么就可以將此時的情況等價于長度為n?1的所有排列中size為m的數量長度為n-1的所有排列中size為m的數量長度為n?1的所有排列中size為m的數量,即(n?1)×Sn?1m(n-1)\times S_{n-1}^m(n?1)×Sn?1m?
- 當數字nnn位于排列的最后一位時,其必然對sizesizesize產生111的貢獻,那么就可以將此時的情況等價于長度為n?1的所有排列中size為m?1的數量長度為n-1的所有排列中size為m-1的數量長度為n?1的所有排列中size為m?1的數量,即Sn?1m?1S_{n-1}^{m-1}Sn?1m?1?
綜上:
Snm=(n?1)×Sn?1m+Sn?1m?1S_{n}^m=(n-1)\times S_{n-1}^m+S_{n-1}^{m-1} Snm?=(n?1)×Sn?1m?+Sn?1m?1?
則答案所求即:
∑i=1ni3?Sni\sum_{i=1}^n i^3·S_{n}^i i=1∑n?i3?Sni?
為了求解答案,我們設sumj[n]=∑i=1nij?Snisum_{j}[n]=\sum_{i=1}^ni^j·S_{n}^isumj?[n]=∑i=1n?ij?Sni?,其中:
- sum0[n]=∑i=1nSni=n!sum_{0}[n]=\sum_{i=1}^nS_{n^i}=n!sum0?[n]=∑i=1n?Sni?=n!
- 所求答案即sum3[n]sum_{3}[n]sum3?[n]
接下來我們來研究sumjsum_{j}sumj?之間的遞推:
sum1[n]=∑i=1ni?Sn?1i帶入遞推式Snm=(n?1)×Sn?1m+Sn?1m?1:=∑i=1ni?[(n?1)Sn?1i+Sn?1i?1]=(n?1)∑i=1ni?Sn?1i+∑i=1ni?Sn?1i?1=(n?1)sum1[n?1]+∑i=0n?1(i+1)?Sn?1i=(n?1)sum1[n?1]+∑i=1n?1i?Sn?1i+∑i=1n?1Sn?1i=(n?1+1)sum1[n?1]+sum0[n?1]sum1[n]=n?sum1[n?1]+sum0[n?1]\begin{align} sum_{1}[n]&=\sum_{i=1}^ni·S_{n-1}^i\\ \\ &帶入遞推式S_{n}^m=(n-1)\times S_{n-1}^m+S_{n-1}^{m-1}:\\ \\ &=\sum_{i=1}^ni·[\ (n-1)S_{n-1}^i+S_{n-1}^{i-1}]\\ \\ &=(n-1)\sum_{i=1}^ni·S_{n-1}^i+\sum_{i=1}^ni·S_{n-1}^{i-1}\\ \\ &=(n-1)sum_{1}[n-1]+\sum_{i=0}^{n-1}(i+1)·S_{n-1}^i\\ \\ &=(n-1)sum_{1}[n-1]+\sum_{i=1}^{n-1}i·S_{n-1}^i+\sum_{i=1}^{n-1}S_{n-1}^i\\ \\ &=(n-1+1)sum_{1}[n-1]+sum_{0}[n-1]\\ \\ sum_{1}[n]&=n·sum_{1}[n-1]+sum_{0}[n-1] \end{align} sum1?[n]sum1?[n]?=i=1∑n?i?Sn?1i?帶入遞推式Snm?=(n?1)×Sn?1m?+Sn?1m?1?:=i=1∑n?i?[?(n?1)Sn?1i?+Sn?1i?1?]=(n?1)i=1∑n?i?Sn?1i?+i=1∑n?i?Sn?1i?1?=(n?1)sum1?[n?1]+i=0∑n?1?(i+1)?Sn?1i?=(n?1)sum1?[n?1]+i=1∑n?1?i?Sn?1i?+i=1∑n?1?Sn?1i?=(n?1+1)sum1?[n?1]+sum0?[n?1]=n?sum1?[n?1]+sum0?[n?1]??
因此我們可以完全類比推導過程得出sumj[n]sum_{j}[n]sumj?[n]的公式:
sumj[n]=∑i=1nij?Sni=∑i=1nij?[(n?1)Sn?1i+Sn?1i?1]=(n?1)sumj[n?1]+∑i=1nij?Sn?1i?1=(n?1)sumj[n?1]+∑i=0n?1(i+1)j?Sn?1i=(n?1)sumj[n?1]+∑i=1n?1∑k=0jCjk?ik?Sn?1i=(n?1)sumj[n?1]+∑k=0jCjk∑i=1n?1ik?Sn?1isumj[n]=(n?1)sumj[n?1]+∑k=0jsumk[n?1]\begin{align} &sum_{j}[n]=\sum_{i=1}^ni^j·S_{n}^i\\ \\ &=\sum_{i=1}^ni^j·[(n-1)S_{n-1}^i+S_{n-1}^{i-1}]\\ \\ &=(n-1)sum_{j}[n-1]+\sum_{i=1}^ni^j·S_{n-1}^{i-1}\\ \\ &=(n-1)sum_{j}[n-1]+\sum_{i=0}^{n-1}(i+1)^j·S_{n-1}^i\\ \\ &=(n-1)sum_{j}[n-1]+\sum_{i=1}^{n-1}\sum_{k=0}^jC_{j}^k·i^k·S_{n-1}^i\\ \\ &=(n-1)sum_{j}[n-1]+\sum_{k=0}^jC_{j}^k\sum_{i=1}^{n-1}i^k·S_{n-1}^i\\ \\ sum_{j}[n]&=(n-1)sum_{j}[n-1]+\sum_{k=0}^jsum_{k}[n-1] \end{align} sumj?[n]?sumj?[n]=i=1∑n?ij?Sni?=i=1∑n?ij?[(n?1)Sn?1i?+Sn?1i?1?]=(n?1)sumj?[n?1]+i=1∑n?ij?Sn?1i?1?=(n?1)sumj?[n?1]+i=0∑n?1?(i+1)j?Sn?1i?=(n?1)sumj?[n?1]+i=1∑n?1?k=0∑j?Cjk??ik?Sn?1i?=(n?1)sumj?[n?1]+k=0∑j?Cjk?i=1∑n?1?ik?Sn?1i?=(n?1)sumj?[n?1]+k=0∑j?sumk?[n?1]??
由此我們可以在o(n)o(n)o(n)的復雜度下遞推得到sum3[n]sum_{3}[n]sum3?[n]
代碼實現
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
const int MOD = 998244353;
const int N = 5e5 + 5;ll ans[N], sum3[N], sum2[N], sum1[N], sum0[N];void precompute() {sum0[1] = 1;sum1[1] = 1;sum2[1] = 1;sum3[1] = 1;ans[1] = 1; for (int n = 2; n < N; ++n) {sum0[n] = (n * sum0[n-1]) % MOD;sum1[n] = (n * sum1[n-1] + sum0[n-1]) % MOD;sum2[n] = (n * sum2[n-1] + 2 * sum1[n-1] + sum0[n-1]) % MOD;sum3[n] = (n * sum3[n-1] + 3 * sum2[n-1] + 3 * sum1[n-1] + sum0[n-1]) % MOD; ans[n] = sum3[n];}
}int main() {ios::sync_with_stdio(false);cin.tie(0);precompute();int T;cin >> T;while (T--) {int n;cin >> n;cout << ans[n] << '\n';}return 0;
}
K. 最大gcd
#數學 #gcd #差分
題目
思路
已知gcd具有特殊性質:
gcd(a1,a2,…,an)=gcd(a1,a2?a1,a3?a2,…,an?an?1)gcd(a_{1},a_{2},\dots,a_{n})=gcd(a_{1},a_{2}-a_{1},a_{3}-a_{2},\dots,a_{n}-a_{n-1}) gcd(a1?,a2?,…,an?)=gcd(a1?,a2??a1?,a3??a2?,…,an??an?1?)
設差分數組b[N],b[i]=a[i]?a[i?1]b[N],b[i]=a[i]-a[i-1]b[N],b[i]=a[i]?a[i?1]
則對于區間[l,r][l,r][l,r]內每個數都加kkk相當于對b[l]+=k,b[r+1]?=kb[l]+=k,b[r+1]-=kb[l]+=k,b[r+1]?=k
- 首先討論整個數組都+k+k+k的情況:
- 操作b[1]+=kb[1]+=kb[1]+=k即可(b[r+1]b[r+1]b[r+1]不存在)
- 此時的gcd為gcd(b1+k,b2,…,bn)gcd(b_{1}+k,b_{2},\dots,b_{n})gcd(b1?+k,b2?,…,bn?)
- 必定存在kkk使得gcd(b2,b3,…,bn)∣(b1+k)gcd(b_{2},b_{3},\dots,b_{n})|(b_{1}+k)gcd(b2?,b3?,…,bn?)∣(b1?+k)
- 因此該情況的gcd記為g1=gcd(b2,b3,…,bn)g_{1}=gcd(b_{2},b_{3},\dots,b_{n})g1?=gcd(b2?,b3?,…,bn?)
- 接下來討論局部+k+k+k的情況:
- 由于是局部操作,所以b1,bnb_{1},b_{n}b1?,bn?這兩個的其中一個必定不會被+k+k+k,因此枚舉b1,bnb_{1},b_{n}b1?,bn?的所有因數fff
- 若所有的bib_{i}bi?都為fff的倍數,那么當前的fff一定是合法的gcd
- 若只有一個bib_{i}bi?不是fff的倍數,那么一定存在一個kkk使得f∣(bi+k)f|(b_{i}+k)f∣(bi?+k),當前的fff也是合法的
- 若超過兩個bib_{i}bi?不是fff的倍數,那么無論怎么操作都不可能使得整個數組的gcd為fff
- 若只有兩個bib_{i}bi?不是fff的倍數,那么需要特殊判斷。在此提供兩種判斷方法:
- 方法一:
- 設不是fff的兩個數為b1,b2b_{1},b_{2}b1?,b2?,則b1=t1×f+b1%f,b2=t2×f+b2%fb_{1}=t_{1}\times f+b_{1}\%f,b_{2}=t_{2}\times f+b_{2}\%fb1?=t1?×f+b1?%f,b2?=t2?×f+b2?%f
- 令k=b1%fk=b_{1}\%fk=b1?%f,則b1?k=t1×fb_{1}-k=t_{1}\times fb1??k=t1?×f為fff的倍數;
- b2+k=t2×f+b1%f+b2%fb_{2}+k=t_{2}\times f+b_{1}\%f+b_{2}\%fb2?+k=t2?×f+b1?%f+b2?%f要為fff的倍數,則必有(b1%f+b2%f)%f=0(b_{1}\%f+b_{2}\%f)\%f=0(b1?%f+b2?%f)%f=0
- 因此可以通過判別式(b1%f+b2%f)%f=0(b_{1}\%f+b_{2}\%f)\%f=0(b1?%f+b2?%f)%f=0來確定兩個bib_{i}bi?是否合法
- 方法二:
- 設不是fff的兩個數為b1,b2b_{1},b_{2}b1?,b2?且b1?k,b2+kb_{1}-k,b_{2}+kb1??k,b2?+k為fff的倍數,則有b1≡k(modf),b2≡(?k)(modf)b_{1}\equiv k\pmod{f},b_{2}\equiv(-k)\pmod{f}b1?≡k(modf),b2?≡(?k)(modf)
- 兩式相加得(b1+b2)≡0(modf)(b_{1}+b_{2})\equiv0\pmod{f}(b1?+b2?)≡0(modf),即(b1+b2)%f=0(b_{1}+b_{2})\%f=0(b1?+b2?)%f=0
- 因此可以通過判別式(b1+b2)%f=0(b_{1}+b_{2})\%f=0(b1?+b2?)%f=0來判斷兩個bib_{i}bi?是否合法
- 方法一:
特別需要注意的是,由于差分可能會導致負數,所以gcd函數返回的時候需要加絕對值
這與給傳入的變量加絕對值是完全不一樣的操作!
代碼實現
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
//#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
const ll inf = 1e7 + 5;
// #define int ll
const int N=1e5+5;ll gcd(ll a,ll b){if(!b)return abs(a);return gcd(b,a%b);
}int a[N],b[N];
void eachT() {int n;cin>>n;bool same=1;int g1;rep(i,1,n){cin>>a[i],b[i]=(a[i]-a[i-1]);if(i==2)g1=b[2];if(i>=3)g1=gcd(g1,(b[i]));if(i>=2&&a[i]!=a[i-1])same=0;}if(same){cout<<0<<'\n';return;}if(n==2){cout<<max(a[1],a[2])<<'\n';return;}set<int>fac;for(int i=1;i*i<=a[1];i++){if(a[1]%i==0)fac.insert(i),fac.insert(a[1]/i);}for(int i=1;i*i<=a[n];i++){if(a[n]%i==0)fac.insert(i),fac.insert(a[n]/i);}int cnt,ans=g1;for(auto&f:fac){int mod=0;cnt=0; rep(i,2,n)if(b[i]%f!=0)cnt++,mod+=b[i]%f;if(cnt<=1){ans=max(ans,f); }if((cnt==2)&&(mod%f==0)){ans=max(ans,f);}}cout<<ans<<'\n';
}
signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);ll t = 1;cin >> t;while (t--) { eachT(); }
}
D.漂亮矩陣
#數學 #組合數 #廣義二項式定理 #FFT
題目
思路
設Bi,j=Ai,j+1?Ai,jB_{i,j}=A_{i,j+1}-A_{i,j}Bi,j?=Ai,j+1??Ai,j?,則有Bi,j≥Bi+1,jB_{i,j}\geq B_{i+1,j}Bi,j?≥Bi+1,j?且∑j=1nBi,j≤m\sum_{j=1}^nB_{i,j}\leq m∑j=1n?Bi,j?≤m
因此有∑j=1nBi+1,j≤∑j=1nBi,j≤∑j=1nB1,j≤m\sum_{j=1}^nB_{i+1,j}\leq \sum_{j=1}^nB_{i,j}\leq \sum_{j=1}^nB_{1,j}\leq m∑j=1n?Bi+1,j?≤∑j=1n?Bi,j?≤∑j=1n?B1,j?≤m
所以只需要滿足∑1,jnB1,j≤m\sum_{1,j}^nB_{1,j}\leq m∑1,jn?B1,j?≤m且每列的BBB值單調不增即可
設B1,i=xB_{1,i}=xB1,i?=x,一列共有nnn個元素,求這一列開頭為xxx且后續單調不增的所有情況數:
- 假設有xxx塊隔板li,i∈[0,x]l_{i},i\in[0,x]li?,i∈[0,x],若li?1l_{i-1}li?1?到lil_{i}li?間有kkk個數,則令這kkk個數為iii
- 由于開頭已經固定為xxx,只能對剩下的n?1n-1n?1個數進行操作,n?1n-1n?1個數提供n?1n-1n?1個空位,再加上xxx個隔板自己的位置,相當于一共x+n?1x+n-1x+n?1個空位中放入xxx個隔板,即Cx+n?1xC_{x+n-1}^xCx+n?1x?
接下來利用生成函數來構造總和不超過mmm的所有情況數:
- 構造函數F(x)=C0+n?10x0+C1+n?11x1+?+Cm+n?1mxm+?=∑i=0∞Ci+n?1ixiF(x)=C_{0+n-1}^0x^0+C_{1+n-1}^1x^1+\dots+C_{m+n-1}^mx^m+\dots=\sum_{i=0}^\infty C_{i+n-1}^ix^iF(x)=C0+n?10?x0+C1+n?11?x1+?+Cm+n?1m?xm+?=∑i=0∞?Ci+n?1i?xi
- 構造多項式F(x)?F(x)?…?F(x)=F(x)n?1F(x)·F(x)·\dots·F(x)=F(x)^{n-1}F(x)?F(x)?…?F(x)=F(x)n?1,相當于除了第一列以外的n?1n-1n?1列的所有情況。多項式F(x)n?1F(x)^{n-1}F(x)n?1中的前mmm項的系數之和即為總和不超過mmm的所有情況數
此時可以用FFTFFTFFT來快速得出答案,復雜度o(nlog?n)o(n\log n)o(nlogn)
但是其實還可以用廣義二項式定理進一步化簡:
廣義二項式定理實際上就是將組合數的定義域拓寬到了整個實數域
F(x)=∑i=0∞Ci+n?1ixi=(1?x)?n(廣義二項式定理)F(x)n?1=(1?x)?n(n?1)令t=n(n?1)則F(x)n?1=(1?x)?t=∑i=0∞Ci+t?1ixi=∑i=0∞Ci+t?1t?1xi則其前m項的系數和為∑i=0mCi+t?1t?1=Cm+tt即求(m+t)!t!?m!=(m+t)(m+t?1)???(t+1)m!\begin{align} &F(x)=\sum_{i=0}^\infty C_{i+n-1}^ix^i=(1-x)^{-n}\ \ \ (廣義二項式定理)\\ \\ &F(x)^{n-1}=(1-x)^{-n(n-1)}\\ \\ &令t=n(n-1) \\ \\ &則F(x)^{n-1}=(1-x)^{-t} =\sum_{i=0}^\infty C_{i+t-1}^ix^i=\sum_{i=0}^\infty C_{i+t-1}^{t-1}x^i\\ \\ &則其前m項的系數和為\sum_{i=0}^mC_{i+t-1}^{t-1}=C_{m+t}^t\\ \\ &即求 \frac{(m+t)!}{t!·m!}= \frac{(m+t)(m+t-1)···(t+1)}{m!} \end{align} ?F(x)=i=0∑∞?Ci+n?1i?xi=(1?x)?n???(廣義二項式定理)F(x)n?1=(1?x)?n(n?1)令t=n(n?1)則F(x)n?1=(1?x)?t=i=0∑∞?Ci+t?1i?xi=i=0∑∞?Ci+t?1t?1?xi則其前m項的系數和為i=0∑m?Ci+t?1t?1?=Cm+tt?即求t!?m!(m+t)!?=m!(m+t)(m+t?1)???(t+1)???
分子可以o(m)o(m)o(m)暴力算出,分母可以o(m)o(m)o(m)預處理逆元算出,總復雜度為o(m)o(m)o(m)
代碼實現
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cmath>
#include<unordered_map>
#include<set>
using namespace std;
using ll = long long;
#define rep(i, a, b) for(ll i = (a); i <= (b); i ++)
#define per(i, a, b) for(ll i = (a); i >= (b); i --)
//#define see(stl) for(auto&ele:stl)cout<<ele<<" "; cout<<'\n';
const ll inf = 1e7 + 5;
// #define int ll
const int N=1e5+5;ll mod=998244353;
ll qpow(ll a, ll b) {a %= mod; ll res = 1;while (b) {if (b % 2) { res *= a, res %= mod; }a *= a, a %= mod, b /= 2;}return res%mod;
}
vector<ll>a, inv;
void inv0(ll len) {a.resize(len + 1), inv.resize(len + 1);a[0] = 1, inv[0] = 1;rep(i, 1, len) {a[i] = a[i - 1] * i % mod;inv[i] = qpow(a[i], mod - 2);}
}void eachT() {ll n,m;cin>>n>>m;ll ans=1;rep(i,1,m){ans*=(n*(n-1)+i)%mod;ans%=mod;}ans*=inv[m];ans%=mod;cout<<ans<<'\n';
}
signed main()
{ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);inv0(5e5);ll t = 1;//cin >> t;while (t--) { eachT(); }
}