【考試總結】2022-03-03
阿新 • • 發佈:2022-03-03
A.排隊 B.暱稱 C.帝國防衛
排隊
\(nm\) 的 \(\rm DP\) 是顯然的,發現每 \(\bmod\) 次轉移係數是一致的,那麼可以合併計算
有了矩陣快速冪的雛形關鍵在於計算矩陣中每個位置的係數,要計算 \(x\) 行的係數可以把數組裡面序號為 \(x\) 的位置置 \(1\) 做一次 \(\rm Dp\) 來實現
複雜度 \(\Theta(m^3\bmod)\)
Code Display
int n,m,tot,dp[2010][20][2][2],id[11][2][2],vec[50],f[2][20][2][2]; struct Mat{ int a[50][50]; Mat(){} }mat; int a[50][50],tmp[50]; signed main(){ freopen("queue.in","r",stdin); freopen("queue.out","w",stdout); n=read(); m=read(); mod=read(); dp[2][1][1][0]=dp[2][1][0][1]=1; for(int i=1;i<=m;++i){ rep(j,0,1) rep(k,0,1) id[i][j][k]=++tot; } int now=2; while((n-now)%mod!=0){ for(int j=1;j<=m;++j){ rep(k,0,1) rep(l,0,1) if(dp[now][j][k][l]){ ckadd(dp[now+1][j+(!k)][1][l],dp[now][j][k][l]); ckadd(dp[now+1][j+(!l)][k][1],dp[now][j][k][l]); int gap=j*2-k-l,rem=now-1-gap; gap%=mod; if(k) gap--,ckadd(dp[now+1][j][0][l],dp[now][j][k][l]); if(l) gap--,ckadd(dp[now+1][j][k][0],dp[now][j][k][l]); ckadd(dp[now+1][j][k][l],mul(dp[now][j][k][l],gap)); ckadd(dp[now+1][j+1][k][l],mul(dp[now][j][k][l],rem%mod)); } } ++now; } if(now==n){ int ans=add(add(dp[n][m][0][0],dp[n][m][0][1]),add(dp[n][m][1][0],dp[n][m][1][1])); print(ans); exit(0); } rep(i,1,m) rep(j,0,1) rep(k,0,1) vec[id[i][j][k]]=dp[now][i][j][k]; int tim=(n-now)/mod; rep(x,1,m) rep(y,0,1) rep(z,0,1){ int cur=0; f[cur][x][y][z]=1; rep(i,now,now+mod-1){ for(int j=1;j<=m;++j){ rep(k,0,1) rep(l,0,1) if(f[cur][j][k][l]){ ckadd(f[cur^1][j+(!k)][1][l],f[cur][j][k][l]); ckadd(f[cur^1][j+(!l)][k][1],f[cur][j][k][l]); int gap=j*2-k-l,rem=i-1-gap; gap%=mod; if(k) gap--,ckadd(f[cur^1][j][0][l],f[cur][j][k][l]); if(l) gap--,ckadd(f[cur^1][j][k][0],f[cur][j][k][l]); ckadd(f[cur^1][j][k][l],mul(f[cur][j][k][l],gap)); ckadd(f[cur^1][j+1][k][l],mul(f[cur][j][k][l],rem%mod)); f[cur][j][k][l]=0; } } cur^=1; } rep(i,1,m) rep(j,0,1) rep(k,0,1) mat.a[id[x][y][z]][id[i][j][k]]=f[cur][i][j][k],f[cur][i][j][k]=0; } while(tim){ if(tim&1){ rep(i,1,tot) rep(j,1,tot) ckadd(tmp[j],mul(vec[i],mat.a[i][j])); rep(i,1,tot) vec[i]=tmp[i],tmp[i]=0; } rep(i,1,tot) rep(k,1,tot) rep(j,1,tot) ckadd(a[i][j],mul(mat.a[i][k],mat.a[k][j])); rep(i,1,tot) rep(j,1,tot) mat.a[i][j]=a[i][j],a[i][j]=0; tim>>=1; } int ans=add(vec[id[m][0][0]],add(vec[id[m][1][1]],add(vec[id[m][1][0]],vec[id[m][0][1]]))); print(ans); return 0; }
暱稱
使用 \(\rm AC\) 自動機實現一個計算不超過 \(\overline{a_1\dots a_n}\) 的數字中有多少個滿足包含給定的 \(S\)
所以我們先二分最後字串的長度,之後按位確定每個數位是幾
注意到一定有連續的一段是 \(S\),那麼對於每個自由位置先判定是不是必須要填 \(S\) 即可,判定次數是自由元個數的級別,也就是 \(\log 10^18\)
那麼複雜度也就降到了 \(\Theta(n^2\log n)\),常數還是蠻大的
Code Display
const int N=2010; int n,fail[N],son[N][10],m; char s[N]; int dp[2][N][2][2],up[N]; //dp[cur][match][bound][pas] inline bool check(int len){ memset(dp,0,sizeof(dp)); int cur=0; dp[cur][0][1][0]=1; for(int i=0;i<len;++i){ for(int j=0;j<=n&&j<=i;++j){ rep(k,0,1) rep(l,0,1) if(dp[cur][j][k][l]){ if(dp[cur][j][k][l]>=m&&l) return 1; if(!l&&len-i<n-j) continue; int Rbound=k?up[i+1]:9; for(int e=0;e<=Rbound;++e){ int nk=k&&(e==up[i+1]); dp[cur^1][son[j][e]][nk][l|(son[j][e]==n)]+=dp[cur][j][k][l]; } dp[cur][j][k][l]=0; } } cur^=1; } int ans=0; for(int i=0;i<=n;++i){ if(ans+dp[cur][i][1][1]>=m) return 1; ans+=dp[cur][i][1][1]; if(ans+dp[cur][i][0][1]>=m) return 1; ans+=dp[cur][i][0][1]; } return 0; } int ans[N]; signed main(){ freopen("nickname.in","r",stdin); freopen("nickname.out","w",stdout); scanf("%s",s+1); n=strlen(s+1); m=read(); son[0][s[1]-'0']=1; for(int i=0;i<n;++i) son[i][s[i+1]-'0']=i+1; for(int i=1;i<=n;++i){ for(int j=0;j<=9;++j){ if(j==s[i+1]-'0') fail[i+1]=son[fail[i]][j]; else son[i][j]=son[fail[i]][j]; } } int l=n,r=n+40,tar=r+1; while(l<=r){ int mid=(l+r)>>1; rep(i,1,n+50) up[i]=0; rep(i,1,mid) up[i]=9; if(check(mid)) r=mid-1,tar=mid; else l=mid+1; } bool Push=0; memset(up,0,sizeof(up)); rep(i,1,tar) ans[i]=9,up[i]=9; for(int i=1;i<=tar;++i){ rep(j,1,tar) up[j]=ans[j]; if(Push==0&&i+n-1<=tar){ for(int j=0;j<n;++j) up[i+j]=s[j+1]-'0'; if(check(tar)){ int ee=i+n-1; while(up[ee]==0) up[ee]=9,--ee; up[ee]--; if(!check(tar)){ rep(j,0,n-1) ans[i+j]=s[j+1]-'0'; i+=n-1; Push=1; continue; } } } rep(j,1,tar) up[j]=ans[j]; l=0,r=8; ans[i]=9; while(l<=r){ int mid=(l+r)>>1; up[i]=mid; if(check(tar)) ans[i]=mid,r=mid-1; else l=mid+1; } } for(int i=1;i<=tar;++i) putchar(ans[i]+'0'); putchar('\n'); return 0; }
帝國防衛
使用整體二分來計算每個點騎士數量不小於 \(c_i\) 的時間,考慮對於每個位置維護 \(Add_i\) 表示距離為 \(i\) 的點在這個點能得到的貢獻,同時維護 \(Del_i\) 表示從哪裡跳上來的要減掉
注意這裡貢獻不能寫作加和右移,因為這和右移之後再加和是完全不一樣的,這也就是為什麼每個深度要單獨維護陣列的原因了
每次修改跳 \(\log\) 個父親,每個父親對應修改其深度即可
剩下的都是整體二分和樹狀陣列 \(\rm dfs\) 序的基礎操作了,不再贅述
時間複雜度是 \(\Theta(n\log^3n)\) 可能通過樹上查分和虛樹將暴力跳父親的過程優化掉
Code Display
const int N=1e5+10;
int n;
struct BIT{
int c[N];
inline void insert(int x){for(;x<=n;x+=x&(-x)) c[x]++; return ;}
inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
}T;
int c[N],fa[N],val[N],in[N],out[N],dfn;
vector<int> G[N],node[N];
inline void get_dfn(int x,int fat){
in[x]=++dfn; fa[x]=fat;
for(auto t:G[x]) if(t!=fat) get_dfn(t,x);
out[x]=dfn;
}
int sum[N],Add[N][19],Del[N][19];
int x[N],delt[N],typ[N],tim[N];
inline int Query(int x){
int res=sum[x],lst=x; x=fa[x];
int up=1;
while(up<=18&&x){
res+=Add[x][up]-Del[lst][up];
++up; x=fa[lst=x];
} return res;
}
inline void insert(int x,int val,int fl){
int now=x,tmp=val;
while(now&&tmp) sum[now]+=tmp*fl,tmp>>=1,now=fa[now];
int lst=0;
while(val&&x){
for(int j=1;val>>j;++j) Add[x][j]+=fl*(val>>j),Del[lst][j]+=fl*(val>>j);
x=fa[lst=x]; val>>=1;
}
return ;
}
inline void solve(int ql,int qr,vector<int> now){
if(ql==qr){
for(auto t:now) tim[t]=ql;
return ;
}
int mid=(ql+qr)>>1;
for(int i=ql;i<=mid;++i) if(typ[i]==1) insert(x[i],delt[i],1);
vector<int> lef,rig;
for(auto e:now) if(Query(e)>=c[e]) lef.push_back(e); else rig.push_back(e);
solve(mid+1,qr,rig);
for(int i=ql;i<=mid;++i) if(typ[i]==1) insert(x[i],delt[i],-1);
solve(ql,mid,lef);
return ;
}
signed main(){
freopen("empire.in","r",stdin); freopen("empire.out","w",stdout);
n=read(); rep(i,1,n) c[i]=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
G[u].push_back(v); G[v].push_back(u);
}
get_dfn(1,0);
int Q=read();
for(int i=1;i<=Q;++i){
typ[i]=read();
if(typ[i]==1) x[i]=read(),delt[i]=read(),insert(x[i],delt[i],1);
else x[i]=read();
}
vector<int> now;
for(int i=1;i<=n;++i) if(Query(i)>=c[i]) now.push_back(i);
for(int i=1;i<=Q;++i) if(typ[i]==1) insert(x[i],delt[i],-1);
solve(1,Q,now);
for(int i=1;i<=n;++i) if(tim[i]) node[tim[i]].push_back(i);
for(int i=1;i<=Q;++i){
if(typ[i]==2){
print(T.query(out[x[i]])-T.query(in[x[i]]-1));
}else{
for(auto t:node[i]) T.insert(in[t]);
}
}
return 0;
}
感覺這套題目許多同學現在考和聯賽前考得分並不會有什麼不一樣