海亮寄 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\) 的合法连续段个数,转移方程形如:

\[f_{i,j} = \sum_{k=2*j}^{n} f_{i-1,k} \]

这个转移很反直觉,转移方式是在开头处扩展!

然后我们令 \(g_i\) 表示长度为 \(i\) 的合法连续段方案数,即:

\[g_i = \sum_{j=1}^{n} f_{i,j} \]

考虑计算答案

发现类似于一个子序列拼接的形式,直接记 \(ans_i\) 表示单词长度为 \(i\) 的答案

枚举最后一个连续段长度,转移即可。具体地,如下:

\[ans_i = \sum_j ans_{i-j} \times g_j \]

(按照分析实现代码)

点击查看代码
#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]&&deg[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\) 段的长度

然后剩下的部分可以就是独立的子问题,形式化地,有:

\[f_n = \sum_{i=0}^{C} \sum_{j=0}^{C-i} f_{l-i-1} \times f_{r-j-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;
}

小结

继续努力!还有很多题没有补!明天还有硬仗要打!

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-16 21:03  sunxuhetai  阅读(15)  评论(0)    收藏  举报