8.17考试总结(NOIP模拟42)[卷·简单题·粉丝·字符串]
你的败因只有一个,就是与我为敌。
T1 卷
解题思路
乍一看,简单的树形 DP 。
后来一看数据范围,发现事实并非如此。(\((10^9)^{2\times 10^5}\)????)
毕竟取 \(\bmod\) 之后的值就可以直接比较大小了。。
第一感觉是高精(当然可以做, 太虚真人 就把这个题用高精干掉了)。
后来发现其实 DP 的值都只是乘积的形式,所以说考虑 \(log\) 运算。
这样就可以完美的把乘运算变为加运算了。
然后我们开两个 DP 数组,一个用来计算 \(log\) 运算,一个用来计算取 \(\bmod\) 之后的值。
不可以用 \(log\) 的运算答案直接作为指数(还有一些奇怪的操作)算答案(反正我是写挂了)
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define double long double
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e5+10,mod=1e9+7;
const double base=log(mod),eps=1e-13,mo=1e9+7;
double f[N][2];
int n,s[N],g[N][2];
int tot=1,head[N],nxt[N<<1],ver[N<<1];
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
double cl(int x)
{
return log(x)/base;
}
void dfs(int x,int fat)
{
f[x][0]=cl(1); f[x][1]=cl(s[x]);
g[x][0]=1; g[x][1]=s[x]%mod;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(to==fat) continue;
dfs(to,x);
if(f[to][0]>f[to][1]) g[x][0]=g[to][0]*g[x][0]%mod;
else g[x][0]=g[to][1]*g[x][0]%mod;
f[x][0]=max(f[to][0],f[to][1])+f[x][0];
f[x][1]=f[to][0]+f[x][1];
g[x][1]=g[to][0]*g[x][1]%mod;
}
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
s[i]=read();
for(int i=1,x,y;i<n;i++)
{
x=read(); y=read();
add_edge(x,y);
add_edge(y,x);
}
dfs(1,0);
if(f[1][0]>f[1][1]) printf("%lld",g[1][0]);
else printf("%lld",g[1][1]);
return 0;
}
T2 简单题
解题思路
首先不难发现数都是分为类似于 \(i,i\times 2^1,i\times 2^2...i\times 2^k\) 这样的形式的,在这里我们姑且把它们视作 链 。
那么对于这些链而言,开头都是 奇数 ,并且链长都是 \(log\) 级别的。
对于每一条链一定是间隔着选,分成固定的两部分。
并且对于长度为偶数的链而言两部分的长度就都是链长的一半。
对于奇数链而言就可以分成两部分,只不过一部分比另一部分多了 1 个数。
然后发现可以组成的集合 A 的大小一定是大于 所有偶数链长度的一半加上所有奇数链较短的一部分所组成的集合大小的,假设最小集合大小为 l 。
每个偶数链的直接贡献是 2 ,剩下的就是 从所有的奇数链中选出 \(m-l\) 个直接卢卡斯。
直接枚举每一种开头的奇数显然是不行的。
我们换一种枚举方式,枚举链的长度,计算长度为它的数量,记为 cnt 。
那么 \(cnt_i=\lfloor\dfrac{\lfloor\frac{n}{2^i}+1\rfloor}{2}\rfloor-cnt_{i+1}\)
然后就可以 \(\mathcal{O(logn)}\) 初始化 对于每一个询问 \(\mathcal{O(1)}\) 求解了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define double long double
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e3+10,M=1e7+10,mod=1e7+19,Lg=70;
int n,m,q,lg,l=0,f[N],cnt[N],all,tot;
int jc[mod+10],inv[mod+10],p2[Lg];
int ys(int x,int y)
{
if(x>y) return 0;
return jc[y]*inv[x]%mod*inv[y-x]%mod;
}
int C(int x,int y)
{
if(!x||x==y) return 1;
if(x>y) return 0;
return ys(x%mod,y%mod)*C(x/mod,y/mod)%mod;
}
int ksm(int x,int y)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%mod;
x=x*x%mod;
y>>=1;
}
return temp%mod;
}
signed main()
{
n=read(); q=read(); lg=log2(n)+1;
jc[0]=inv[0]=p2[0]=1;
for(int i=1;i<mod;i++) jc[i]=jc[i-1]*i%mod;
inv[mod-1]=ksm(jc[mod-1],mod-2);
for(int i=mod-2;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
for(int i=1;i<=lg;i++) p2[i]=p2[i-1]*2;
for(int i=0;i<=lg;i++) cnt[i]=(n/p2[i]+1)/2;
for(int i=0;i<=lg;i++) cnt[i]-=cnt[i+1];
for(int i=0;i<=lg;i++)
if((i+1)&1)
tot+=cnt[i];
for(int i=1;i<=lg;i++)
l+=cnt[i]*((i+1)/2);
all=(n+1)>>1;
int base=ksm(2,all-tot)%mod;
while(q--)
{
m=read();
if(m<l) printf("0\n");
else printf("%lld\n",base*C(m-l,tot)%mod);
}
return 0;
}
T3 粉丝
解题思路
有两种计算划分数的 DP
\(f_{i,j}\)表示当前 DP 到数字 i 也就是最大的数字为 i ,总和为 j 的方案数。
转移方程就是:\(f_{i,j}=f_{i-1,j}+f_{i-1,j-k}(k\in[1,i])\)
\(g_{i,j}\)表示当前分成了 i 个数字,总和为 j 的方案数
转移方程就是:\(g_{i,j}=g_{i-1,j}+g_{i,j-i}\)。
注意上述的所有方程都暂时不考虑起始为 \([x,y]\) 的限制。
我们就可以固定下界,上界直接设为 n ,然后就类似与求了一个后缀,减一下就好了。
这两个 dp 分别使用都是 \(\mathcal{O(n^2)}\) 的,但是假设我们令\(B=\sqrt{n}\) ,把所有 \(\le B\) 的数字都拿出来使用 f 的那个 dp。
那么剩下的数字都必然 \(>B\) ,因此最多被划分成 \(\dfrac{n}{B}\) 个数字,此时使用 g 来 dp。
最后合并两个 DP 的值,复杂度为 \(O(n\sqrt{n})\)
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1e5+10;
int ans,n,mod,l,r,f[N],g[N],cnt[N];
int work(int x)
{
int ans=0,ending=max(x,(int)sqrt(n));
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(cnt,0,sizeof(cnt));
f[0]=g[0]=cnt[0]=1;
for(int i=x;i<ending;i++)
for(int j=0;j<=n-i;j++)
f[i+j]=(f[i+j]+f[j])%mod;
for(int i=1;i<=n/ending;i++)
{
for(int j=0;j<=n-i*ending-i;j++)
g[i+j]=(g[i+j]+g[j])%mod;
for(int j=0;j<=n-i*ending;j++)
cnt[j+i*ending]=(cnt[j+i*ending]+g[j])%mod;
}
for(int i=0;i<=n;i++)
ans=(ans+f[i]*cnt[n-i]%mod)%mod;
return ans;
}
signed main()
{
l=read(); r=read(); n=read(); mod=read();
printf("%lld",((l<=n?work(l):0)-(r<n?work(r+1):0)+mod)%mod);
return 0;
}
T4 字符串
解题思路
Manacher+KMP
首先对于两端相等的部分是一定要选上的。
然后对于剩下的部分可以看做 \(B+D\) 或者 \(A+C\) ,正反做两遍就好了。
先是对于原串跑一边 Manacher 搞出每个位置的最大的回文半径。
接下来利用 KMP 的 next 数组求出字符串的 border 。(这里需要把原串反转一下在放到最后)
答案就变成了对于原串的每一个字符而言,自身最大的回文半径再加上两倍的 next 数组,也就是我们所求的 border 在整个字符串的两端各放一个。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5e6+10;
char s[N],km[N<<1],ma[N<<1];
int n,ans,cntk,cntm,tot,p[N<<1],nxt[N<<1],mx[N<<1];
void Manacher()
{
memset(p,0,sizeof(p));
ma[cntm=0]='~'; ma[++cntm]='|';
for(int i=1;i<=n;i++)
{
ma[++cntm]=s[i];
ma[++cntm]='|';
}
for(int i=1,r=0,mid=0;i<=cntm;i++)
{
if(i<=r) p[i]=min(p[mid*2-i],r-i+1);
while(ma[i-p[i]]==ma[i+p[i]]) p[i]++;
if(p[i]+i-1>=r) r=p[i]+i-1,mid=i;
}
}
void KMP()
{
cntk=n;
memcpy(km+1,s+1,sizeof(char)*n);
km[++cntk]='$';
for(int i=n;i>=1;i--)
km[++cntk]=s[i];
memset(nxt,0,sizeof(nxt));
memset(mx,0,sizeof(mx));
for(int i=cntk-1,j=0;i>=1;i--)
{
while(j>0&&km[i]!=km[cntk-j]) j=nxt[j];
if(km[i]==km[cntk-j]) j++;
nxt[i]=j;
}
for(int i=n;i>=1;i--)
mx[i]=max(mx[i+1],nxt[i]);
}
void solve()
{
Manacher(); KMP();
for(int i=1;i<=cntm;i++)
{
int l=(i-p[i])>>1,r=(i+p[i])>>1;
if(nxt[r]<=l) ans=max(ans,p[i]+2*nxt[r]-1);
if(mx[r]>=l) ans=max(ans,p[i]+2*l-1);
}
}
signed main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n/2;i++)
if(s[i]==s[n-i+1]) tot++;
else break;
if(tot>=n/2){printf("%lld",n);return 0;}
n-=2*tot;
for(int i=1;i<=n;i++)
s[i]=s[i+tot];
solve();reverse(s+1,s+n+1);solve();
printf("%lld",ans+2*tot);
return 0;
}