CSP模拟7
考的好像还行,主要是别人T1炸了一片;
A. 卷
非常普通的树上dp的板子题,就是把加变成了乘,因为乘这个东西比较的恶心,乘两三下就爆long long,所以我们把乘法改成加法。
根据这个小学二年级的知识:
logab+logac=loga(b*c)
转成加法比较,维护乘法的值。
复杂度O(n)

#include <iostream> #include <cstdio> #include <cstring> #include <cmath> const int MAXN=2e5+10; const int mod=1e9+7; const double eps=1e-10; using namespace std; int n,cnt; long long w[MAXN]; struct edge { int to,next; }a[MAXN<<1]; int head[MAXN]; long long f[MAXN][2]; long double f1[MAXN][2]; void add(int u,int v) { a[++cnt].to=v; a[cnt].next=head[u]; head[u]=cnt; } void dfs(int now,int fa) { f[now][0]=f[now][1]=1; f1[now][0]=f1[now][1]=0; f[now][1]=w[now]%mod; f1[now][1]+=log(w[now]); for(int i=head[now];i;i=a[i].next) { int v=a[i].to; if(v==fa) continue; dfs(v,now); f[now][1]=f[now][1]*f[v][0]%mod; f1[now][1]=f1[now][1]+f1[v][0]; if(f1[v][1]-f1[v][0]>eps) { f[now][0]=f[now][0]*f[v][1]%mod; f1[now][0]=f1[now][0]+f1[v][1]; } else { f[now][0]=f[now][0]*f[v][0]%mod; f1[now][0]=f1[now][0]+f1[v][0]; } } return; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lld",&w[i]); } for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs(1,0); if(f1[1][1]-f1[1][0]>eps) { printf("%lld",f[1][1]%mod); } else { printf("%lld",f[1][0]%mod); } return 0; }
B. 简单题
考虑数是如何分的,我们发现,对于一个奇数 x,我们可以把它想象成一个奇数链,链上的元素为 x,x*2,x*4,x*8.......x*2k(x*2k<=n)
这个链上的元素在A还是在B只与链上的其他元素有关,与其他链上的元素无关。
而且对于每一条链,链内元素谁和谁在一起也是固定的,举个例子:x与x*2不能在同一边,x*2与x*4不能在同一边,那x与x*4就必然在同一边;
根据这个性质我们发现,对于长度为偶数的链(下面成为偶数链)是对半分的,是能且只能这么分(但不确定这一半是给了A还是B),所以对于单个偶数链,它对答案的贡献为*2;
再看奇数(长度)链,相当于有一半比另一半多一个元素,.A与B被分得的长度也是因为多的这一个元素而不同;
这时我们发现,一个人被分的长度是有范围的,设偶数链个数为a,奇数链个数为b,n就是题目中那个n;
被分得的长度len范围为:[(n-b)/2,(n-b)/2+b];
也就是把最短的都给他,和最长的都给他。
因为长度其实是由奇数链决定的,所以所有奇数链的贡献为 C(b,m-l) (m为题中的m,l是上面的那个极小值)
现在我们就要求出奇数链的个数;
总链数为 (n+1)/2;; 所以让n不断地/2,除一次,记录现在还有多少条链,与下次的相减,就是在这个长度结束的链,在分别记录偶数链和奇数链就可以了。
复杂度O(log n);

#include <iostream> #include <cstdio> #include <cmath> const int mod=10000019; using namespace std; inline long long read() { long long f=0,x=1; char ch=getchar(); while(ch>'9' || ch<'0') { if(ch=='-') { f=-1; } ch=getchar(); } while(ch>='0' && ch<='9') { //x=x*10+ch-'0'; x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } long long n,m,l,r; int q,sum,lg; int cnt[10000010]; long long jc[mod<<1],inv[mod<<1],invj[mod<<1],mi[110]; long long C(long long n,long long m) { if(m>n) return 0; return jc[n]%mod*invj[n-m]%mod*invj[m]%mod; } long long Lucas(long long n,long long m) { if(!m || n==m) return 1; if(m>n) return 0; return Lucas(n/mod,m/mod)%mod*C(n%mod,m%mod)%mod; } long long fpow(long long x,int k) { long long res=1; while(k) { if(k&1) res=res*x%mod; x=x*x%mod; k>>=1; } return res%mod; } void init() { lg=log2(n)+1; jc[0]=1,jc[1]=1,inv[1]=1,invj[0]=1,invj[1]=1; for(int i=2;i<=mod;i++) { jc[i]=jc[i-1]%mod*i%mod; inv[i]=(mod-(mod/i))%mod*inv[mod%i]%mod; invj[i]=invj[i-1]*inv[i]%mod; } mi[0]=1; for(int i=1;i<=lg;i++) { mi[i]=mi[i-1]*2; } } int main() { scanf("%lld%d",&n,&q); init(); for(int i=0;i<=lg;i++) { cnt[i]=(n/mi[i]+1)/2; } for(int i=0;i<=lg;i++) { cnt[i]-=cnt[i+1]; if((i+1)&1) { sum+=cnt[i]; } } l=(n-sum)>>1; r=l+sum; long long total=(n+1)>>1; long long g=fpow(2,total-sum)%mod; while(q--) { // m=read(); scanf("%lld",&m); if(m<l || m>r) { printf("0\n"); } else { printf("%lld\n",g%mod*Lucas(sum,m-l)%mod); } } return 0; }
C. 粉丝
有两种计算划分数的 DP
fi,j表示当前 DP 到数字 i 也就是最大的数字为 i ,总和为 j 的方案数。
转移方程就是:fi,j=fi−1,j+fi−1,j−k(k∈[1,i])
gi,j表示当前分成了 i 个数字,总和为 j 的方案数
转移方程就是:gi,j=gi−1,j+gi,j−i
注意上述的所有方程都暂时不考虑起始为 [x,y]的限制。
我们就可以固定下界,上界直接设为 n ,然后就类似与求了一个后缀,减一下就好了。
这两个 dp 分别使用都是 O(n2))的,但是假设我们令B=√n ,把所有 ≤B≤ 的数字都拿出来使用 f 的那个 dp。
那么剩下的数字都必然 >B> ,因此最多被划分成 nB个数字,此时使用 g 来 dp。
最后合并两个 DP 的值,复杂度为 O(n√n)

#include <iostream> #include <cstdio> #include <cmath> #include <cstring> const int MAXN=1e5+5; using namespace std; int x,y,n,mod; long long f[MAXN],g[400][MAXN]; long long solve(int x) { memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); f[0]=1, g[0][0]=1; int l1=sqrt(n); for(int i=x;i<=l1;i++) { for(int j=i;j<=n;j++) { f[j]=(f[j]+f[j-i])%mod; } } int l2=max(l1,x-1); for(int i=0;i<=l1;i++) { for(int j=0;j<=n;j++) { if(i && i+j<=n) { g[i][i+j]=g[i][i+j]+g[i][j]; g[i][i+j]%=mod; } if(j+l2+1<=n) { g[i+1][j+l2+1]=g[i+1][j+l2+1]+g[i][j]; g[i+1][j+l2+1]%=mod; } } } long long ans=0; for(int i=0;i<=l1;i++) { for(int j=0;j<=n;j++) { ans=(ans+g[i][j]*f[n-j]%mod)%mod; } } return ans; } int main() { scanf("%d%d%d%d",&x,&y,&n,&mod); printf("%lld",(solve(x)-solve(y+1)+mod)%mod); return 0; }
D. 字符串
首先把两边一样的先删去,记录下来。
剩下的为A,B,C,D 或者B,C,D,E;
考虑A,B,C,D,答案为A+C;即使A+C最长;
找出的回文串一定是两种情况;(其中P为回文串)
- A=M+P ,P与N之间是没有用的串, C=N ,M=reverse(N)
- A=M ,M和P之间是没有用的串, C=P+N, M=reverse(N);
考虑在剩下的字符中用Manacher找以每个字符为中心的最长回文串,在回文串的两头接上最长的M和N;
至于M,N怎么求,先把原串镜像,倒着跑KMP就行了,可以自己手摸一下;
复杂度O(n)

#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> const int MAXN=5e6+5; using namespace std; int n,len,ans,fro; char s[MAXN],s1[MAXN<<1],s2[MAXN<<1]; int p[MAXN<<1],nxt1[MAXN<<1],nxt[MAXN<<1],mnxt[MAXN<<1]; void Manacher() { memset(p,0,sizeof(p)); len=0; int r=0,c=0; s1[0]='$'; s1[++len]='#'; for(int i=1;i<=n;i++) { s1[++len]=s[i]; s1[++len]='#'; } s1[++len]='*'; for(int i=1;i<len;i++) { if(i<r) { p[i]=max(p[c<<1-i],p[c]+c-i); } else p[i]=1; while(s1[i+p[i]]==s1[i-p[i]]) { p[i]++; } if(i+p[i]>r) { c=i; r=p[i]; } } } void Kmp() { memset(nxt,0,sizeof(nxt)); memset(mnxt,0,sizeof(mnxt)); int len1=0; for(int i=1;i<=n;i++) { s2[++len1]=s[i]; } s2[++len1]='|'; for(int i=n;i>=1;i--) { s2[++len1]=s[i]; } /*for(int i=len1-1;i>=1;i--) { int j=nxt[i+1]; while(j && s2[i]!=s2[len1-j]) { j=nxt[j]; } if(s2[i]==s2[len1-j]) { j++; } nxt[i]=j; cout<<nxt[i]<<" "; } cout<<endl;*/ for(int i=2,j=0;i<=len1;i++) { while(j && s2[i]!=s2[j+1]) { j=nxt1[j]; } if(s2[i]==s2[j+1]) { j++; } nxt1[i]=j; } for(int i=1;i<=len1;i++) { nxt[i]=nxt1[len1-i+1]; } for(int i=n;i>=1;i--) { mnxt[i]=max(mnxt[i+1],nxt[i]); } } void work() { for(int i=1;i<len;i++) { int l=(i-p[i])/2 ,r=(i+p[i])/2; if(nxt[r]<=l) ans=max(ans,p[i]-1+2*nxt[r]); if(mnxt[r]>=l) ans=max(ans,p[i]-1+2*l); } } int main() { scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n/2;i++) { if(s[i]==s[n-i+1]) { fro++; } else break; } if(fro==n/2) { printf("%d",n); return 0; } n-=fro*2; for(int i=1;i<=n;i++) { s[i]=s[i+fro]; } Manacher(); Kmp(); work(); reverse(s+1,s+1+n); Manacher(); Kmp(); work(); printf("%d",ans+fro*2); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义