【比赛记录】2025CSP-S模拟赛10

A. 返乡
当所有 \(a+b+c\) 都相等时,显然没有偏序。使这个和为 \(\lfloor\frac{3\times n}{2}\rfloor\) 时,显然是数量最多的。
然后实际上这就是最优构造,因为它显然无法再往里加东西了。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
int n;
vector<pair<pair<int,int>,int> > p;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
int sum=n*3/2;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
int k=sum-i-j;
if(k>=0&&k<=n){
p.pb(mp(mp(i,j),k));
}
}
}
cout<<p.size()<<"\n";
for(auto x:p){
cout<<x.fir.fir<<" "<<x.fir.sec<<" "<<x.sec<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
B. 连接
首先有一个性质,我们取的钢管一定至少有一端在分界线上。且如果另一端不在分界线上,总质量必然取到了 \(L\) 或 \(R\)。原因是如果没有取到,就一定可以向大密度方向继续延伸(或将小密度钢管缩掉)。
于是我们只消分别计算这两种情况。对于只有一端是分界线的情况,我们可以枚举这个分界线,然后二分出另一端的位置。注意分界线有可能在左边或右边。对于两端都是分界线的情况,我们直接去二分答案,枚举左端点并双指针出右端点所在的区间,判断能否取到比 \(mid\) 更大的密度即可。
具体的判断,进行一个前缀长度和 \(sa\) 和前缀质量和 \(sm\),如果能更大,那么一定有:
所以用单调队列维护 \(sm_i-mid\times sl_i\) 的最大值即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int n,ll,rr,a[maxn],b[maxn],sa[maxn],sm[maxn],q[maxn];
double ans;
il void solve(){
for(int i=1;i<=n;i++){
sa[i]=sa[i-1]+a[i];
sm[i]=sm[i-1]+a[i]*b[i];
}
for(int i=1,l,r;i<=n;i++){
// cout<<i<<"\n";
l=i,r=n;
while(l<r){
int mid=(l+r)>>1;
if(sm[mid]-sm[i-1]<ll){
l=mid+1;
}
else{
r=mid;
}
}
if(sm[l]-sm[i-1]>=ll){
// cout<<l<<" "<<ll/(sa[l-1]-sa[i-1]+(ll-sm[l-1]+sm[i-1])*1.0/b[l])<<"\n";
ans=max(ans,ll/(sa[l-1]-sa[i-1]+(ll-sm[l-1]+sm[i-1])*1.0/b[l]));
}
l=i,r=n;
while(l<r){
int mid=(l+r)>>1;
if(sm[mid]-sm[i-1]<rr){
l=mid+1;
}
else{
r=mid;
}
}
if(sm[l]-sm[i-1]>=rr){
// cout<<l<<" "<<rr/(sa[l-1]-sa[i-1]+(rr-sm[l-1]+sm[i-1])*1.0/b[l])<<"\n";
ans=max(ans,rr/(sa[l-1]-sa[i-1]+(rr-sm[l-1]+sm[i-1])*1.0/b[l]));
}
}
}
signed main(){
scanf("%lld%lld%lld",&n,&ll,&rr);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
}
for(int i=1;i<=n;i++){
scanf("%lld",b+i);
}
solve();
// cout<<ans<<"\n";
reverse(a+1,a+n+1);
reverse(b+1,b+n+1);
solve();
double L=1,R=1e6;
while(R-L>1e-7){
double mid=(L+R)/2;
for(int i=1,l=1,r=0,hd=1,tl=0;i<=n;i++){
while(r<n&&sm[r+1]-sm[i-1]<=rr){
r++;
while(hd<=tl&&sm[r]-sa[r]*mid>=sm[q[tl]]-sa[q[tl]]*mid){
tl--;
}
q[++tl]=r;
}
while(l<n&&sm[l]-sm[i-1]<ll){
l++;
while(hd<=tl&&q[hd]<l){
hd++;
}
}
if(hd>tl||sm[l]-sm[i-1]<ll||sm[r]-sm[i-1]>rr){
continue;
}
if(sm[q[hd]]-sa[q[hd]]*mid>sm[i-1]-sa[i-1]*mid){
L=mid;
goto togo;
}
}
R=mid;
togo:;
}
printf("%.8f",max(ans,L));
return 0;
}
}
signed main(){return asbt::main();}
C. 习惯孤独
考虑第 \(i\) 次切要切掉的子树大小,为 \(a_{i-1}-a_i\)。切掉的子树就没用了,故这 \(k\) 次切割就成了相互独立的了。\(k\) 又那么小,考虑状压 DP。
设 \(f_{u,S}\) 表示 \(u\) 的子树,切割状态为 \(S\) 的方案数。那么用儿子更新父亲是容易的。然后还要考虑 \(u\) 本身。这时需要注意,\(u\) 的切割是必须放在 \(u\) 的子树之后的。
然后还得考虑 \(u\) 子树外的贡献,显然需要换根。但是这个形式,直接从总贡献中剔除一个点是比较困难的。因此需要对 \(u\) 的儿子做前后缀和。注意此时还得再算上 \(u\) 单独的贡献。
最后统计答案,我们要求 \(u\) 最后留下,所以不能用 \(f\) 统计答案。需要另计一个 \(g\) 来计算 \(u\) 的子树除了 \(u\) 的贡献。最后答案需要再除以 \(a_k\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=5e3+5,maxm=(1<<6)+5,mod=998244353;
typedef int DP[maxn][maxm];
int n,m,U,a[10],sz[maxn],hp[maxm];
DP f,g,pre,suf,h;
vector<int> e[maxn];
il int qpow(int x,int y=mod-2){
int res=1;
while(y){
if(y&1){
res=res*1ll*x%mod;
}
x=x*1ll*x%mod,y>>=1;
}
return res;
}
il void dfs1(int u,int fa){
sz[u]=1,f[u][0]=1;
int fid=-1;
for(int i=0,v;i<e[u].size();i++){
v=e[u][i];
if(v==fa){
fid=i;
continue;
}
dfs1(v,u);
sz[u]+=sz[v];
for(int j=0;j<=U;j++){
hp[j]=f[u][j];
f[u][j]=0;
// cout<<hp[j]<<" ";
}
// puts("");
for(int j=0,S;j<=U;j++){
S=U^j;
for(int k=S;;k=(k-1)&S){
(f[u][j|k]+=hp[j]*1ll*f[v][k]%mod)%=mod;
if(!k){
break;
}
}
}
}
if(~fid){
e[u].erase(e[u].begin()+fid);
}
for(int i=0;i<=U;i++){
g[u][i]=f[u][i];
}
for(int i=0,sum;i<=U;i++){
sum=0;
for(int j=1;j<=m;j++){
if(i>>(j-1)&1){
sum+=a[j];
}
}
sum=sz[u]-sum;
for(int j=m;j;j--){
if(i>>(j-1)&1){
break;
}
if(sum==a[j]){
(f[u][i|1<<(j-1)]+=g[u][i])%=mod;
}
}
}
}
il void dfs2(int u){
for(int i=0;i<=e[u].size()+1;i++){
for(int j=0;j<=U;j++){
pre[i][j]=suf[i][j]=0;
}
}
pre[0][0]=suf[e[u].size()+1][0]=1;
for(int i=0,v;i<e[u].size();i++){
v=e[u][i];
for(int j=0,S;j<=U;j++){
S=U^j;
for(int k=S;;k=(k-1)&S){
(pre[i+1][j|k]+=pre[i][j]*1ll*f[v][k]%mod)%=mod;
if(!k){
break;
}
}
}
}
for(int i=e[u].size(),v;i;i--){
v=e[u][i-1];
for(int j=0,S;j<=U;j++){
S=U^j;
for(int k=S;;k=(k-1)&S){
(suf[i][j|k]+=suf[i+1][j]*1ll*f[v][k]%mod)%=mod;
if(!k){
break;
}
}
}
}
for(int i=0,v;i<e[u].size();i++){
v=e[u][i];
for(int j=0,S;j<=U;j++){
S=U^j;
for(int k=S;;k=(k-1)&S){
(h[v][j|k]+=pre[i][j]*1ll*suf[i+2][k]%mod)%=mod;
if(!k){
break;
}
}
}
for(int j=0;j<=U;j++){
hp[j]=h[v][j];
h[v][j]=0;
}
for(int j=0,S;j<=U;j++){
S=U^j;
for(int k=S;;k=(k-1)&S){
(h[v][j|k]+=hp[j]*1ll*h[u][k]%mod)%=mod;
if(!k){
break;
}
}
}
for(int j=0;j<=U;j++){
hp[j]=h[v][j];
}
for(int j=0,sum;j<=U;j++){
sum=0;
for(int k=1;k<=m;k++){
if(j>>(k-1)&1){
sum+=a[k];
}
}
sum=n-sz[v]-sum;
for(int k=m;k;k--){
if(j>>(k-1)&1){
break;
}
if(sum==a[k]){
(h[v][j|1<<(k-1)]+=hp[j])%=mod;
}
}
}
}
for(int v:e[u]){
dfs2(v);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
cin>>m;
U=(1<<m)-1;
for(int i=1;i<=m;i++){
cin>>a[i];
}
a[0]=n;
for(int i=m+1;i;i--){
a[i]=a[i-1]-a[i];
}
dfs1(1,0);
h[1][0]=1;
dfs2(1);
// puts("666");
int ans=0;
for(int u=1;u<=n;u++){
// cout<<u<<"\n";
for(int i=0;i<=U;i++){
hp[i]=0;
}
for(int i=0,S;i<=U;i++){
// cout<<f[u][i]<<" "<<g[u][i]<<" "<<h[u][i]<<"\n";
S=U^i;
for(int j=S;;j=(j-1)&S){
(hp[i|j]+=g[u][i]*1ll*h[u][j]%mod)%=mod;
if(!j){
break;
}
}
}
(ans+=hp[U])%=mod;
}
cout<<ans*1ll*qpow(a[m+1])%mod;
return 0;
}
}
int main(){return asbt::main();}

浙公网安备 33010602011771号