海亮寄 7.16
前言
业精于勤荒于嬉,行成于思毁于随
正文(模拟赛)
卦象:吉
感受:我(消音)(消音)(消音)(消音)的编译器,(消音)(消音)的,我(消音)(消音)(消音)的 VS code。这次模拟赛没啥策略可言。T1 不会正解,无奈打了 40pts 的部分分数,后来看题解感觉凭借自己虚弱的精神状态很难场切。T2 涉及到基环树,直接跳过下一题。T3 只会 80pts(算错复杂度,以为矩阵快速幂是正解),然而由于 VS code 的神秘问题导致运行时各种出错,从而白白浪费至少 80min 的时间。但最后经过各种操作之后,代码的结果可视化,并且本人有绝对的自信是编译器的问题(最后也顺利获得了相应分数),遂放弃调试。其实中间有想过再去看别的题,因为死磕并不明智。但是转念一想,T1 已经投入了接近 100min,T2 属于知识盲区,T3 出现了神秘的编译器问题,只剩一道 T4。个人认为很难在 50min 内在 T4 上做出有建设性的成果,并且为了避免评测拥挤,还得提前于考试结束交题。所以继续死磕 T3,效果是不错的,剩余 20min 时,写了 T4 的 50pts 暴力,最后没有挂分,获得了 40pts + 0pts + 80pts + 50pts = 170 pts / 400pts 的成绩。然而,赛后 5min 看出了 T4 正解,真是天道好轮回,又一次因为决策失误痛失 50pts(但或许这不是个人原因?因为 T3 的代码其实很早就可以 AC 了,只是编译器一直在作妖不知道犯什么病,由于断网也用不了在线 IDE)。总结来说,个人认为模拟赛表现尚可,日后扬长避短叭!
T1
\(O(n^2)\) 的 DP 不多赘述,接下来——
注!意!到!
题意定义的新单词可以划分为若干连续段,使得连续段内是严格单调上升的
发现这样的连续段个数可能极多,但长度是 \(O(\log n)\) 量级,所以可以在这上面做手脚
记 \(f_{i,j}\) 表示长度为 \(i\) 且开头字符为 \(j\) 的合法连续段个数,转移方程形如:
这个转移很反直觉,转移方式是在开头处扩展!
然后我们令 \(g_i\) 表示长度为 \(i\) 的合法连续段方案数,即:
考虑计算答案
发现类似于一个子序列拼接的形式,直接记 \(ans_i\) 表示单词长度为 \(i\) 的答案
枚举最后一个连续段长度,转移即可。具体地,如下:
(按照分析实现代码)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,K=21,MOD=1e9+7;
int n,m,k,f[K][N],g[N],suf[N],ans[N];
inline void ADD(int &x,int y){x=(x+y)%MOD;return;}
signed main(){
freopen("lan.in","r",stdin);
freopen("lan.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;k=__lg(n)+1;
for(int j=n/2+1;j<=n;j++)f[1][j]=1;
for(int i=2;i<=k;i++){
for(int j=n;j>=1;j--)suf[j]=(suf[j+1]+f[i-1][j])%MOD;
for(int j=1;j<=n;j++)ADD(f[i][j],suf[j*2]);
}
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
ADD(g[i],f[i][j]);
ans[0]=1;
for(int i=1;i<=m;i++)
for(int j=1;j<=min(i,k);j++)
ADD(ans[i],ans[i-j]*g[j]%MOD);
cout<<ans[m]<<'\n';
return 0;
}
T2
建模出内向基环树,发现不好做,改向变为外向基环树
先计算最大暗恋数。需要分讨,对于一个连通块:
-
如果只有一个环,那么最大暗恋数就是 环上结点数 \(-1\)
-
如果是环上挂若干个树结构,最大暗恋数就是有出度的点的个数
再计算方案数。
依旧是先考虑只有一个环,那么环上的任意一个结点都可以作为起点,答案是 环上结点数
剩下的一般结构,答案是 图的排列方案数 \(\times (n-ans1!)\),其中 \(ans1\) 是最大暗恋数
解释一下这个式子
式子的第二项表示,最大暗恋数会给整张图断开的连通块数,对于任意一个连通块,他们之间没有顺序,可以随意排列。
而式子的第一项表示,对于基环上的结点,其如果有出度,那么他们就会有等于其出度种选择,然而需要刨除恰好是一个基环的情况;而对于非基环上的结点,则不需要刨除任何贡献,直接乘出度即可
分析结束,交给代码!
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e6+5,MOD=1e9+7;
int n,c[N],deg[N],ans1,ans2;vi G[N];
bool ring[N],mrk[N],vis[N];
inline int get(int u){
if(ring[u])return u;
ring[u]=true;
return get(c[u]);
}
inline void dfs(int u){
if(vis[u])return;
vis[u]=true;
for(int v:G[u])dfs(v);
return;
}
signed main(){
freopen("single.in","r",stdin);
freopen("single.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;ans1=n,ans2=1;
for(int i=1;i<=n;i++)cin>>c[i],deg[c[i]]++,G[c[i]].pb(i);
for(int i=1;i<=n;i++)
if(!deg[i])ans1--;
for(int i=1;i<=n;i++){
if(vis[i])continue;
int k=get(i);dfs(k);
int res=1,len=0,x=k;bool flag=true;
while(true){
len++;
if(deg[x]>1)flag=false;
mrk[x]=true;res=(res*deg[x])%MOD;
if(c[x]==k)break;
x=c[x];
}
if(flag)ans1--;
res=(flag?len:(res-1+MOD)%MOD);
ans2=(ans2*res)%MOD;
}
for(int i=1;i<=n;i++)
if(!mrk[i]&°[i]>0)ans2=(ans2*deg[i])%MOD;
for(int i=1;i<=n-ans1;i++)ans2=(ans2*i)%MOD;
cout<<ans1<<' '<<ans2<<'\n';
return 0;
}
T3
赛场上一眼瞪出 80pts 矩阵快速幂做法
正解 (消音)(消音)的是分治!!!
DP 数组不多讲,考虑求 \(f_n\) 的值
发现这个东西可以对半劈开,枚举左半边右端极长连续 \(1\) 段的长度,以及右半边左端极长连续 \(1\) 段的长度
然后剩下的部分可以就是独立的子问题,形式化地,有:
前缀和优化一下,降掉一个 \(O(C)\)
写个记搜,用 unordered_map 记一下,时间复杂度 \(O(C^2 \times \log n)\),常数极大
(补题时遇到了神秘的数组与 vector 实现结果不一样的情况,目前不知道原因,只知道 vector 可过而数组不可过……)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e6+5,K=505,MOD=1e9+7;
int n,c,f[N],sum[N];
unordered_map<int,int> F;
inline void ADD(int &x,int y){
x%=MOD,y%=MOD,x=(x+y)%MOD;
return;
}
inline int solve(int n){
if(n==-1)return 1;
if(n<=1000000)return f[n+1];
if(F[n])return F[n];
int l=n/2,r=n-l,res=0;
vi vec;vec.pb(solve(l-1));
for(int i=1;i<=c;i++)vec.pb((vec[i-1]+solve(l-i-1))%MOD);
for(int i=0;i<=c;i++)ADD(res,vec[c-i]*solve(r-i-1)%MOD);
// for(int i=0;i<=c;i++)s[i]=0;
// for(int i=0;i<=c;i++)s[i]=solve(l-i-1)%MOD;
// for(int i=1;i<=c;i++)ADD(s[i],s[i-1]);
// for(int i=0;i<=c;i++)ADD(res,s[c-i]*solve(r-i-1)%MOD);
return F[n]=res;
}
signed main(){
freopen("nuclear.in","r",stdin);
freopen("nuclear.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>c;
f[0]=1;
sum[0]=f[0]=1;
for(int i=1;i<=c;i++){
f[i]=sum[i-1];
sum[i]=(sum[i-1]+f[i])%MOD;
}
for(int i=c+1;i<N;i++){
f[i]=(sum[i-1]-sum[i-c-2]+MOD)%MOD;
sum[i]=(sum[i-1]+f[i])%MOD;
}
cout<<solve(n)<<'\n';
return 0;
}
T4
博弈论 + 打表观察 + 清新性质题
(简直 BUFF 叠满有木有)
打表发现,对于给定的 \(n\),答案 \(ans_n\) 是 \(n\) 不断减去小于它的最大的斐波那契数,直到剩余一个斐波那契数
(不会证明)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=88;
int n,f[N];
signed main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
f[1]=1,f[2]=2;
for(int i=3;i<N;i++)f[i]=f[i-1]+f[i-2];
cin>>n;
for(int i=N-1;i>=1;i--){
if(n==f[i]){cout<<n<<'\n';return 0;}
if(n>f[i])n-=f[i];
}
return 0;
}
小结
继续努力!还有很多题没有补!明天还有硬仗要打!
后记
世界孤立我任它奚落
完结撒花!

浙公网安备 33010602011771号