lhx: 為啥你不會線性篩,這麼重要的東西
lhx: 你不篩積性函式嗎
我: 我一般做數學題推不到需要上線性篩的地方
lhx: 哦那你確實用不到
A.新的階乘
找質數 \(p\) 在式子中的冪,其實就是帶權找 \(p\) 在 \([1,n]\) 所有數中的出現次數
發現 \([1,n]\) 中所有數只有形如 \(kp\) 的數能夠被找出因子 \(p\)
因此嘗試列舉所有 \(p\) 的倍數來更新 \(p\) 的答案
發現這玩意和埃氏篩天作之合,你列舉倍數的時候順便就把質數篩了,考慮直接上埃氏篩
因為不會怎麼求因子出現個數於是用了複雜度很假的暴力做法
理論 \(n\log\log n\log n\),實測 0.3s
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
bool notprime[10000001];
vector<int>ans;
inline int powof(int i,int j){
int res=0;
while(j){
if(j%i==0) j/=i,res++;
else break;
}
return res;
}
signed main(){
cin>>n;
notprime[0]=true;
notprime[1]=true;
for(int i=2;i<=n;++i){
if(notprime[i]==false){
ans.push_back(i);
int cnt=n-i+1;
for(int j=2;j*i<=n;++j){
notprime[i*j]=true;
cnt+=(powof(i,j)+1)*(n-j*i+1);
}
ans.push_back(cnt);
}
}
cout<<"f("<<n<<")=";
if(n<=1) cout<<n;
for(int i=0;i<=(int)ans.size()-1;i+=2){
if(i!=0) cout<<'*';
if(ans[i+1]==1){
cout<<ans[i];
}
else{
cout<<ans[i]<<'^'<<ans[i+1];
}
}
}
B.博弈樹
寫爆搜打表找規律發現:所有節點中,Bob 至多隻有一個
爆搜
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[1001];
int deep[1001],fa[10][1001],w[10][1001];
void dfs(int now,int last){
deep[now]=deep[last]+1;
fa[0][now]=last;
for(int i:e[now]){
if(i!=last){
w[0][i]=1;
dfs(i,now);
}
}
}
inline void prework(){
for(int i=1;i<=9;++i){
for(int j=1;j<=n;++j){
fa[i][j]=fa[i-1][fa[i-1][j]];
w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
}
}
}
inline int dis(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int res=0;
for(int i=9;i>=0;--i){
if(deep[fa[i][x]]>=deep[y]){
res+=w[i][x];
x=fa[i][x];
}
}
if(x==y) return res;
for(int i=9;i>=0;--i){
if(fa[i][x]!=fa[i][y]){
res+=w[i][x]+w[i][y];
x=fa[i][x];
y=fa[i][y];
}
}
return res+w[0][x]+w[0][y];
}
//true = win
bool bydfs(int now,int lastdis){
for(int i=1;i<=n;++i){
if(i!=now){
int tmp=dis(i,now);
if(tmp>lastdis){
if(bydfs(i,tmp)==false) return true;
}
}
}
return false;
}
int main(){
cin>>n>>q;
for(int i=1;i<=n-1;++i){
int x,y;cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
prework();
while(q--){
int t;cin>>t;
cout<<(bydfs(t,0)?"Alice":"Bob")<<'\n';
}
}
而且這個點離樹的中心極其近
額外發現當樹的重心有兩個的時候,沒有 Bob
嘗試直接特判樹的重心假完了
手摸了出錯的資料發現,應該是自己樹的重心這個思路不對,考慮到 Bob 和 Alice 的博弈過程,這裡應該是某種和樹上距離有關的 “重心”
於是想出了正確的結論
- 定義重心為使得到所有點的距離的最大值最小的節點
- 如果重心只有一個,Bob 會在此處獲勝,否則均為 Alice 獲勝
證明:
考慮如果先手在直徑的一個端點上,可以透過移動到另一個端點來直接勝出,否則不能移動到直徑的端點上,否則後手會移動到另一個端點而直接勝出
考慮刪除了原樹所有直徑的端點的樹,如果初始點在這棵樹上為直徑的某一個端點,那麼也一定是先手必勝的,因為先手可以將點移動到直徑另一個端點,這樣後手就一定會將點移動到原樹的直徑端點上,並且移動的長度一定小於原樹直徑,這樣先手就可以將點移動到原樹的另一個直徑端點取得勝利
因此後手勝利,當且僅當先手無論如何都會移動到某層的直徑端點上,這樣的情況只會在只存在一個點不在某層的直徑端點上的圖上,並且該點恰好為重心
但是這個東西卻是 \(n^2\) 的,過不了 \(10^5\)
但是你沒必要去列舉所有的點,因為重心一定在樹的直徑上,而且一定是直徑上靠近中點的位置,你直接去判斷靠近直徑中點那幾個點就行了
被 \(n=1\) 卡了,\(n=1\) 無論如何都是 Bob 贏
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[100001];
vector<int>zx;
int size[100001];
int maxsonsize[100001];
int deep[100001],fa[20][100001],w[20][100001];
void dfs(int now,int last){
deep[now]=deep[last]+1;
fa[0][now]=last;
for(int i:e[now]){
if(i!=last){
w[0][i]=1;
dfs(i,now);
}
}
}
inline void prework(){
for(int i=1;i<=19;++i){
for(int j=1;j<=n;++j){
fa[i][j]=fa[i-1][fa[i-1][j]];
w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
}
}
}
inline int dis(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int res=0;
for(int i=19;i>=0;--i){
if(deep[fa[i][x]]>=deep[y]){
res+=w[i][x];
x=fa[i][x];
}
}
if(x==y) return res;
for(int i=19;i>=0;--i){
if(fa[i][x]!=fa[i][y]){
res+=w[i][x]+w[i][y];
x=fa[i][x];
y=fa[i][y];
}
}
return res+w[0][x]+w[0][y];
}
int minsize=0x7fffffff;
void dfs2(int i){
int maxdis=0;
for(int j=1;j<=n;++j){
maxdis=max(maxdis,dis(i,j));
}
if(maxdis<minsize){
minsize=maxdis;
zx={i};
}
else if(maxdis==minsize){
zx.push_back(i);
}
}
int dis5[100001];
int dis6[100001];
int maxdis5,maxdis5id;
int maxdis6,maxdis6id;
void dfs5(int now,int last){
for(int i:e[now]){
if(i!=last){
dis5[i]=dis5[now]+1;
if(dis5[i]>maxdis5){
maxdis5=dis5[i];
maxdis5id=i;
}
dfs5(i,now);
}
}
}
void dfs6(int now,int last){
for(int i:e[now]){
if(i!=last){
dis6[i]=dis6[now]+1;
if(dis6[i]>maxdis6){
maxdis6=dis6[i];
maxdis6id=i;
}
dfs6(i,now);
}
}
}
vector<int>zj;
bool dfs7(int now,int last,int tar){
if(now==tar){
zj.push_back(now);
return true;
}
for(int i:e[now]){
if(i!=last){
if(dfs7(i,now,tar)){
zj.push_back(now);
return true;
}
}
}
return false;
}
void dfs8(){
int tmp=(int)zj.size()/2;
int tmp2=tmp-1;
if(tmp>=0) for(int i:e[zj[tmp]]) dfs2(i);
if(tmp2>=0) for(int i:e[zj[tmp2]]) dfs2(i);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin>>n>>q;
if(n==1){
while(q--){
cout<<"Bob\n";
}
return 0;
}
for(int i=1;i<=n-1;++i){
int x,y;
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
prework();
dfs5(1,0);
dfs6(maxdis5id,0);
dfs7(maxdis5id,0,maxdis6id);
dfs8();
while(q--){
int t;cin>>t;
if(zx.size()!=1ull) cout<<"Alice\n";
else if(zx[0]!=t) cout<<"Alice\n";
else cout<<"Bob\n";
}
}
C.劃分
先說 corner case
- \(n=k\)
顯然了,只有唯一的劃分方式
- 字串最前面有超過 \(k\) 位全是 \(0\)
這個時候直接在前面劃就行了,無論你怎麼拆後面帶 \(1\) 的字串都只會讓答案更小
設前面有 \(p\) 個 \(0\),劃分成 \(k\) 段,需要插 \(k-1\) 塊板,注意最後一個 \(0\) 後面也是能插的,答案即為 \(\sum^p_{i=k-1}C_{p}^{i}\),預處理階乘直接算即可
注意這個 case 有一個全 \(0\) 的特殊情況,在第三個 hack
- 通解
上面兩個 corner case 都要判掉再做通解
最優解一定形如選出 \(k-1\) 個單個的區間,剩下一個連續的大區間單獨分一段(區間越長高位貢獻越大)
這樣剩餘的情況就只有 \(k\) 種了,可以暴力判斷哪一種情況更優
因為 \(1\) 的個數一樣,實際上誰的高位 \(1\) 個數更多,誰的答案就更大
因此只比較那個長區間,直接暴力比較是 \(O(k)\) 的,為了規避暴力比較,可以用二分+雜湊求出需要比較的兩個區間的最長公共字首,然後直接比較後一位,可以做到 \(O(\log k)\)
這裡注意最長公共字首等於原串的情況,有可能會越界
還是那句話,誰的高位 \(1\) 個數更多,誰的答案就更大,因此我們判方案數的時候也是同理,因為最低位的 \(1\) 和單獨的 \(1\) 貢獻是一樣的,因此兩個串答案相同,當且僅當最長的區間去除最低位後相等,依舊用雜湊判斷
注意卡模數的問題
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
const int P1=1e9+3,P2=1e7+0721;
int n,k;
string x;
int power(int a,int t){
int base=a,ans=1;
while(t){
if(t&1){
ans=ans*base%p;
}
base=base*base%p;
t>>=1;
}
return ans;
}
int fact[2000001];
int C(int n,int m){
return fact[n]*power(fact[m]*fact[n-m]%p,p-2)%p;
}
int cal(int l,int r){
int res=0;
for(int i=l;i<=r;++i){
res=(res*2+x[i]-'0')%p;
}
return res;
}
const unsigned long long num=233,num2=2333;
unsigned long long h[2000001],basenum[2000001];
unsigned long long geth(int l,int r){
if(l>r) return 0;
return (h[r]-h[l-1]*basenum[r-l+1]%P1+P1)%P1;
}
unsigned long long h2[2000001],basenum2[2000001];
unsigned long long geth2(int l,int r){
if(l>r) return 0;
return (h2[r]-h2[l-1]*basenum2[r-l+1]%P2+P2)%P2;
}
bool great(int now,int ori){
int l=0,r=n-k+1,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(geth(now,now+mid-1)==geth(ori,ori+mid-1) and geth2(now,now+mid-1)==geth2(ori,ori+mid-1)){
l=mid+1;
ans=mid;
}
else r=mid-1;
}
if(ans==n-k+1) return false;
return x[now+ans]>x[ori+ans];
}
inline int cal2(int l,int r){
int res=0;
for(int i=1;i<l;++i){
res=(res+x[i]-'0')%p;
}
int res2=0;
for(int i=l;i<=r;++i){
res2=(res2*2+x[i]-'0')%p;
}
res=(res+res2)%p;
for(int i=r+1;i<=n;++i){
res=(res+x[i]-'0')%p;
}
return res;
}
signed main(){
freopen("divide.in","r",stdin);
freopen("divide.out","w",stdout);
cin>>n>>k>>x;x=" "+x;
if(n==k){
int res=0;
for(int i=1;i<=n;++i){
res=(res+x[i]-'0')%p;
}
cout<<res<<" "<<1;
return 0;
}
fact[0]=1;
basenum[0]=1;
basenum2[0]=1;
for(int i=1;i<=n;++i){
fact[i]=fact[i-1]*i%p;
h[i]=(h[i-1]*num+x[i])%P1;
basenum[i]=basenum[i-1]*num%P1;
h2[i]=(h2[i-1]*num2+x[i])%P2;
basenum2[i]=basenum2[i-1]*num2%P2;
}
int first1=n;
for(int i=1;i<=n;++i){
if(x[i]=='1'){
first1=i;
break;
}
}
if(first1>=k){
cout<<cal(1,n)<<' ';
int res=0;
for(int i=k-1;i<=first1-1;++i){
res=(res+C(first1-1,i))%p;
}
cout<<res;
return 0;
}
int maxid=0;
for(int i=1;i<=k;++i){
if(maxid==0) maxid=i;
else{
if(great(i,maxid)){
maxid=i;
}
}
}
cout<<cal2(maxid,maxid+n-k)<<" ";
int res=0;
for(int i=1;i<=k;++i){
if(geth(i,i+n-k-1)==geth(maxid,maxid+n-k-1) and geth2(i,i+n-k-1)==geth2(maxid,maxid+n-k-1)) res++;
}
cout<<res;
}