10月杂题题解
发现这里面好多题都是重量级。。。(现在已经是只加重量级了)
CF814E An unavoidable detour for home
其实是对这篇 题解 的一些理解。
Part 1
不难发现最终图大致长这样:
考虑一棵最短路树,以结点 1 为根,往下每一层有若干个结点,表示最短路距离相同的一些编号连续的结点。
其中每一层内部可以自由连边。
除了每层内部的连边和树边,其余边不合法。
Part 2
考虑第
设当前层有
考虑把度数为 2 的点拆为 2 个度数为 1 的点。
设内部连了
考虑如何计算
先让
个点选 个任意排列,钦定相邻两两一对连边。 取消相邻两点的顺序,例如边
是等价的。考虑所有可能重复的情况,就是 。 取消边之间的排列顺序,即
。
即:
由于拆了点,所以拆的点之间会出现重边和自环。
枚举出现了
设这些不合法的边涉及到的边数
从
就是选
那么现在选了
所以还要乘上选择这
注意还要取消拆点的顺序,还是考虑所以可能重复的情况,就是
记得容斥一下,那么有
Part 3
设
则原式可以写为:
设
那么可以枚举
注意上面那个式子,前面只需要枚举
那么记一个辅助数组
时间复杂度
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005,p=1e9+7,iv2=(p+1)/2;
int n,d[N],f[N][N*2],g[N][N*2];
int t[N],fac[N*2]={1},ifac[N*2],ipw[N*2]={1};
int qpow(int a,int b) {int r=1;for(;b;b>>=1,a=a*a%p) if(b&1) r=r*a%p;return r;}
int inv(int x) {return qpow(x,p-2);}
void inc(int &x,int y) {if((x+=y)>=p) x-=p;}
void mul(int &x,int y) {if((x*=y)>=p) x%=p;}
void init()
{
for(int i=1;i<=2000;i++)
fac[i]=fac[i-1]*i%p,
ipw[i]=ipw[i-1]*iv2%p;
ifac[2000]=inv(fac[2000]);
for(int i=2000-1;~i;i--) ifac[i]=ifac[i+1]*(i+1)%p;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>d[i];
init();
for(int s=0;s<=n;s++)
for(int x=0;x*2<=s;x++)
{
int y=s-x*2;
inc(t[s],ifac[x]*ifac[y]%p*(((x+y)&1)?p-1:1)%p);
}
f[1][d[1]]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=i*2;j++)
{
//g[i][j]->f[i][j-2(z-s)]
if(!g[i][j]) continue;
for(int k=0;k*2<=j;k++)//枚举 z-s
inc(f[i][j-k*2],g[i][j]*ipw[k]%p*ifac[k]%p);
}
for(int j=1;j<=n-i;j++)
{
if(!f[i][j]) continue;
int x=0,y=0;
for(int k=i+1;k<=i+j;k++) d[k]==2?x++:y++;
for(int s=0;s<=y;s++) inc(g[i+j][x+2*y-2*s],fac[y]*fac[x+2*y-2*s]%p*t[s]%p*ifac[y-s]%p*ipw[y]%p*f[i][j]%p);
}
}
cout<<f[n][0];
}
C0930 T4 铺设道路
随便补的一场 C 组模拟赛,前三题太水,T4 还有点意思。
神仙贪心。
考虑求出
那么每次操作相当于选择一对
最少天数显然是
对于一个位置
由于是差分数组,显然每个
这时只需要令
剩下的
对于
那么应该让消去剩下这些
这里考虑令代价最大的情况,那每次对于
这个过程可以用队列维护。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5,p=1e9+7;
int n,t,ans1,ans2,a[N];
deque<pair<int,int>> q1,q2;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=n;i;i--) a[i]-=a[i-1];
for(int i=1;i<=n;i++) t+=max(a[i],0ll);
a[n+1]=-1e18;
for(int i=1;i<=n+1;i++)
{
if(a[i]>0) q1.push_back({a[i],i}),q2.push_back({a[i],i});
else
{
int x=-a[i];
while(x&&q1.size())
{
int &y=q1.front().first,l=q1.front().second;
if(x<y) y-=x,ans1=(ans1+(i-l)*(i-l)%p*x)%p,x=0;
else ans1=(ans1+(i-l)*(i-l)%p*y)%p,x-=y,q1.pop_front();
}
x=-a[i];
while(x&&q2.size())
{
int &y=q2.back().first,l=q2.back().second;
if(x<y) y-=x,ans2=(ans2+(i-l)*(i-l)%p*x)%p,x=0;
else ans2=(ans2+(i-l)*(i-l)%p*y)%p,x-=y,q2.pop_back();
}
}
}
cout<<t<<'\n'<<ans2<<'\n'<<ans1<<'\n';
}
CF1799H Tree Cutting
又一道神仙 dp。话说为什么 3200 是紫啊。。。
无非分为两种操作:保留一个子树,或删除一个子树。
k 很小,考虑状压。
设
但是保留或者删除一个子树会影响,比如不能同时保留两棵子树。
发现只要有保留子树的操作,那么就只需要独立考虑这个子树了。
多加一维状态,设
若
考虑合并
设
那么转移合法当且仅当:
第二个就是避免保留两棵不同子树的情况。
第三个就是对于非子树 u 内的操作要在保留 u 子树之前,当然也可以反着。
然后考虑加入
要么是保留了
枚举当前对应的操作是
- 如果是删除子树,那么
。 - 如果是保留子树,那么
。
当然还有最基本的子树大小限制。考虑对于
那么枚举
实际写起来是有很多技巧的,需要慢慢体会。
#include<bits/stdc++.h>
using namespace std;
const int N=5005,K=(1<<6)+1,p=998244353;
int n,k,ans,a[N],sz[N],mx[N],f[N][8][K],g[8][K];
vector<int> G[N];
void inc(int &x,int y) {if((x+=y)>=p) x-=p;}
void dfs(int u,int fa)
{
f[u][k+1][0]=sz[u]=1;
for(int v:G[u])
{
if(v==fa) continue;
dfs(v,u);
sz[u]+=sz[v];
memset(g,0,sizeof(g));
for(int i=1;i<=k+1;i++)
for(int S=0;S<1<<k;S++)
{
int rS=(1<<k)-1-S;
for(int T=rS;;T--,T&=rS)
{
if(mx[T]<i) inc(g[i][S|T],1ll*f[u][i][S]*f[v][k+1][T]%p);
if(mx[S]<i&&i!=k+1) inc(g[i][S|T],1ll*f[u][k+1][S]*f[v][i][T]%p);
if(!T) break;
}
}
for(int i=1;i<=k+1;i++)
for(int S=0;S<1<<k;S++)
f[u][i][S]=g[i][S];
}
if(u!=1)
{
for(int i=1;i<=k+1;i++)
for(int S=0;S<1<<k;S++)
g[i][S]=f[u][i][S];
for(int i=1;i<=k+1;i++)
for(int S=0;S<1<<k;S++)
if(f[u][i][S])
for(int j=1;j<i;j++)
if(!(S&(1<<j-1)))
{
int _sz=sz[u];
for(int k=1;k<j;k++) if(S&(1<<k-1)) _sz-=a[k-1]-a[k];
if(_sz==a[j]) inc(g[j][S|(1<<j-1)],f[u][i][S]);
if(_sz==a[j-1]-a[j]&&mx[S]<j) inc(g[i][S|(1<<j-1)],f[u][i][S]);
}
for(int i=1;i<=k+1;i++)
for(int S=0;S<1<<k;S++)
f[u][i][S]=g[i][S];
}
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int x,y;cin>>x>>y;
G[x].push_back(y),G[y].push_back(x);
}
cin>>k;a[0]=n;
for(int i=1;i<=k;i++) cin>>a[i];
for(int i=1;i<1<<k;i++) for(int j=1;j<=k;j++) if(i&(1<<j-1)) mx[i]=j;
dfs(1,0);
for(int i=1;i<=k+1;i++) inc(ans,f[1][i][(1<<k)-1]);
cout<<ans;
}
AGC028E High Elements
众所周知 noip T3 一般是 3500,而昨天模拟赛是 AT 4100,很合理。
又是神仙 dp + 神仙结论。
当然是贺的这篇题解。
由于让字典序最小,从前往后考虑,看第
设原排列里的前缀最大值为旧的,否则为新的。
如果一种方案合法,必然可以使得
因为如果
考虑这个结论有什么用。
先假设
设
左边是常数。右边就相当于:令旧的权值为
可以发现如果前缀最大值序列里有旧的,那么不选这个旧的也不会影响合法性,只会令权值
即若能凑出
所以只用关心权值为奇/偶数的能凑出的最大值。这个可以用线段树优化 dp 维护。
上述是假设
最后如果
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,ans[N],p[N],s[N],old[N];
#define lc (k<<1)
#define rc (k<<1|1)
#define mid (l+r>>1)
int Mx[N<<2][2];// 0:even 1:odd
void upd(int p,int x,int op,int k=1,int l=1,int r=n)
{
if(l==r) {Mx[k][op]=x;return;}
p<=mid?upd(p,x,op,lc,l,mid):upd(p,x,op,rc,mid+1,r);
Mx[k][op]=max(Mx[lc][op],Mx[rc][op]);
}
int qmax(int x,int y,int op,int k=1,int l=1,int r=n)
{
if(l>=x&&r<=y) return Mx[k][op];
int res=-1e9;
if(x<=mid) res=qmax(x,y,op,lc,l,mid);
if(mid<y) res=max(res,qmax(x,y,op,rc,mid+1,r));
return res;
}
bool chk(int i,int w)
{
if(w<0) return 0;
return qmax(i,n,w&1)>=w;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i];
int mx=0;
for(int i=1;i<=n;i++) if(p[i]>mx) mx=p[i],old[i]=1;
for(int i=n;i>=1;i--) s[i]=s[i+1]+old[i];
for(int i=1;i<=n*4;i++) Mx[i][1]=-1e9;
for(int i=n;i>=1;i--)
{
int t0=qmax(p[i],n,0),t1=qmax(p[i],n,1);
if(old[i]) upd(p[i],t0+2,0),upd(p[i],t1+2,1);
else upd(p[i],t1+1,0),upd(p[i],t0+1,1);
}
int cx=0,cy=0,mxx=0,mxy=0;
for(int i=1;i<=n;i++)
{
upd(p[i],0,0),upd(p[i],-1e9,1);
if(chk(mxy,cx+(p[i]>mxx)-cy+s[i+1])||chk(max(mxx,p[i]),cy-cx-(p[i]>mxx)+s[i+1]))
ans[i]=0,cx+=p[i]>mxx,mxx=max(p[i],mxx);
else ans[i]=1,cy+=p[i]>mxy,mxy=max(p[i],mxy);
}
if(cx!=cy) puts("-1");
else for(int i=1;i<=n;i++) cout<<ans[i];
}
P3343 地震后的幻想乡
你能想象这是一次 noip 模拟赛的 T2。
想了半天,打开洛谷题解一看,最高票是_rqy的,一堆密密麻麻的积分差点把我吓跑。
据说有三种解法,然而我只学会了一种最辣鸡的凡人解法。
Part 1
简略证一下这个提示:
- 对于
个 之间随机变量 ,第 小的那个期望值是 。
考虑引入第
考虑对这
总共可以插入
Part 2
利用这个提示,考虑比较暴力的做法。
假设我们知道这
从小到大一条一条加边,直到加入第
也就是说加入前
那么这个最小生成树的最大边权的排名就是
考虑计算最小生成树中的最大边权的期望排名。
设这条边在
等价于:
一开始我这个傻逼还不理解为什么,然后这不就是拆成最朴素求和的形式吗?
发现
可以计算加入前
根据上,不难定义状态:
考虑如何转移。
可以枚举一个连通的子集
但如果存在一对
一个计数的套路,以某个
画画图大概就知道这样不重不漏了?
Part 3
上述要枚举连通的子集
设
显然有
答案为
#include<bits/stdc++.h>
#define int long long
using namespace std;
double ans;
int n,m,u[50],v[50];
int C[50][50],sz[1100],f[1100][50][2];
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>u[i]>>v[i];
for(int i=0;i<1<<n;i++) for(int j=1;j<=m;j++) if((i&(1<<u[j]-1))&&(i&(1<<v[j]-1))) sz[i]++;
for(int i=0;i<=m;i++) {C[i][0]=C[i][i]=1;for(int j=1;j<i;j++) C[i][j]=C[i-1][j]+C[i-1][j-1];}
for(int S=0;S<1<<n;S++)
{
int k=S&(-S);
for(int T=(S-1)&S;T;T--,T&=S)
{
if(!(T&k)) continue;
for(int i=0;i<=sz[T];i++)
for(int j=0;j<=sz[S^T];j++)
f[S][i+j][0]+=f[T][i][1]*C[sz[S^T]][j];
}
for(int i=0;i<=sz[S];i++) f[S][i][1]=C[sz[S]][i]-f[S][i][0];
}
for(int i=0;i<m;i++) ans+=1.0*f[(1<<n)-1][i][0]/C[m][i];
printf("%.6f",ans/(m+1));
}
CF605E Intergalaxy Trips
相对简单,但本人期望太垃圾,还是记一记。
套路的,设
因为是最优策略,所以每次一定是选最优的转移。
假设当前
题意是可以走自环的,所以如果不存在
设
那么有:
移项得:
然后你发现之后无论如何
所以这个 dp 其实是有顺序的,每次找最小的
细节上由于
然后最坑的一点,直接读入 double 非常慢,会 TLE。(怪不得题目不直接输入 double 类型)
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,x,vis[N];
double f[N],g[N],p[N][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>x,p[i][j]=x/100.0;
for(int i=1;i<=n;i++) f[i]=1,g[i]=1-p[i][n];
if(n==1) {cout<<0;return 0;}
vis[n]=1;
for(int i=1;i<=n;i++)
{
int x=0;
double mn=1e18;
for(int j=1;j<=n;j++)
if(!vis[j]&&f[j]<mn*(1-g[j]))
x=j,mn=f[j]/(1-g[j]);
vis[x]=1;
if(x==1) break;
for(int j=1;j<=n;j++)
f[j]+=mn*p[j][x]*g[j],g[j]*=1-p[j][x];
}
printf("%.10f",f[1]/(1-g[1]));
}
ABC245H Product Modulo 2
小难数学题。
设
考虑
若
否则,考虑前
考虑
若
显然一组可能没有或有多个
对于剩下的,其实就是上面的情况,只不过不能填
方案数:
若
方案数:
考虑一般情况怎么做。
根据算数基本定理,
对于每个
其实这有点像 CRT,感性理解一下。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
int k,n,m,ans=1,inv[60];
int qpow(int a,int b) {int r=1;for(a%=mod;b;b>>=1,a=a*a%mod) if(b&1) r=r*a%mod;return r;}
int C(int n,int m)
{
int res=1;
for(int i=1;i<=m;i++) res=res*(n-m+i)%mod*inv[i]%mod;
return res;
}
int calc(int n,int m,int p,int c)
{
int pw=qpow(m-m/p,k-1);
if(n!=0)
{
int x=0;
while(n%p==0) n/=p,x++;
return C(x+k-1,x)*pw%mod;
}
else
{
int res=qpow(m,k);
for(int i=0;i<c;i++) res=(res-(qpow(p,c-i)-qpow(p,c-i-1)+mod)%mod*C(i+k-1,i)%mod*pw%mod+mod)%mod;
return res;
}
}
signed main()
{
cin>>k>>n>>m;
inv[1]=1;for(int i=2;i<=50;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i*i<=m;i++)
if(m%i==0)
{
int _m=1,c=0;
while(m%i==0) _m*=i,c++,m/=i;
ans=ans*calc(n%_m,_m,i,c)%mod;
}
if(m>1) ans=ans*calc(n%m,m,m,1)%mod;
cout<<ans<<'\n';
}
B1019 T4 非攻
csp-s rp++!
推推你的式子 /yim !(推式子能力还是太菜了啊)
说实话 50 pts 很好拿。
考虑把一个排列分为几个置换环,就是
比如
设环的大小为
贪心的,每次用环内最小值交换,最小代价为最小值乘上剩余元素之和。
考虑枚举
推到 (1) 就能拿 50 pts 了,赞美良心出题人。
稍微说一下 (1),使用笨蛋列举法。
假设
,枚举当前集合大小 。
,那么所有集合情况为: 。
,那么所有集合情况为: 。
,那么所有集合情况为: 。 可以发现每个元素出现次数是一样的,于是只用计算一个元素的出现次数。
集合有
种可能,钦定一个元素必须选,那么有 种可能。
继续推后面那一坨。
说一下倒数第二行的
考虑在
带回原式就可以
题解说还有基于分治 NTT &@*¥%¥&,可以进一步优化到
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e7+5,p=1e9+7;
int n,ans,fac,inv[N];
signed main()
{
cin>>n;
inv[1]=1;for(int i=2;i<=n;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
fac=1 ;for(int i=1;i<=n;i++) fac=fac*i%p;
for(int i=1;i<n;i++) ans=(ans+1ll*(n+i+1)%p*(n-i)%p*fac%p*inv[i+1]%p)%p;
cout<<ans*500000004%p;
}
B1023 T3 彩排
神仙构造题。
不妨倒着构造,那么题意可转化为:
给你一个
怎么个方法呢?大概是这样:
一开始
除去
考虑遍历当前组,假设当前位置在
,即 的值是当前位置的目标,直接交换 和 的值。 ,找到一个 且 ,交换 和 的值。
第一种很好理解,而第二种你让
注意
发现对于一个大小为
#include<bits/stdc++.h>
using namespace std;
const int N=1005,M=6e5+5;
int n,m,x,a[N],b[M];
bool chk() {for(int i=2;i<=n;i++) if(a[i]!=n-i+2) return 0;return 1;}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
reverse(a+1,a+1+n);
x=a[1];
for(int i=1;i<=n;i++) b[++m]=a[i];
while(!chk())
{
for(int i=2;i<=n;i++)
{
if(x==n-i+2||(x==1&&a[i]!=n-i+2)) swap(x,a[i]);
b[++m]=a[i];
}
}
b[++m]=1;
reverse(b+1,b+1+m);
cout<<m<<'\n';
for(int i=1;i<=m;i++) cout<<b[i]<<' ';
}
B1026 T4 挖矿
不知道 cqbz 给的这两套 noip 模拟赛题是什么 jb。
不过这是道很好的数据结构题,虽然好像是原。
假设一个区间合法,那么
然后可以 ST 表 + 扫描线
不过正解和这个没有关系。
考虑只涂黑权值在
厉害的来了,考虑所有
正确性显然,画画图就知道了。
从小到大枚举
显然有
对于每次增加一个
好像有更智慧的写法,可惜我不智慧。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,V;
long long ans;
pair<int,int> pos[N];
vector<vector<int>> a;
#define lc (k<<1)
#define rc (k<<1|1)
#define mid (l+r>>1)
int mn[N<<2],num[N<<2],add[N<<2];
void pushup(int k)
{
mn[k]=min(mn[lc],mn[rc]);
num[k]=0;
if(mn[lc]==mn[k]) num[k]+=num[lc];
if(mn[rc]==mn[k]) num[k]+=num[rc];
}
void addtag(int k,int v) {mn[k]+=v,add[k]+=v;}
void pushdown(int k)
{
if(!add[k]) return;
addtag(lc,add[k]),addtag(rc,add[k]);
add[k]=0;
}
void upd(int x,int y,int v,int k=1,int l=1,int r=V)
{
if(l>r) return;
if(l>=x&&r<=y) {addtag(k,v);return;}
pushdown(k);
if(x<=mid) upd(x,y,v,lc,l,mid);
if(mid<y) upd(x,y,v,rc,mid+1,r);
pushup(k);
}
void point_init(int x,int k=1,int l=1,int r=V)
{
if(l==r) {mn[k]=4;num[k]=1;return;}
x<=mid?point_init(x,lc,l,mid):point_init(x,rc,mid+1,r);
pushup(k);
}
void work(int a,int b,int c,int d)
{
if(!b) b=1e9;if(!c) c=1e9;if(!d) d=1e9;
if(c<b) swap(c,b);if(d<b) swap(b,d);if(d<c) swap(c,d);
//r=a 其余四个值从小到大排序
int cnt=0;
if(d<a) {upd(d+1,a-1,(cnt==1||cnt==3)?-1:1);cnt++;}
if(c<a) {upd(c+1,min(a-1,d),(cnt==1||cnt==3)?-1:1);cnt++;}
if(b<a) {upd(b+1,min(a-1,c),(cnt==1||cnt==3)?-1:1);cnt++;}
if(b<a) upd(1,b,(cnt==1||cnt==3)?-1:1);
if(b>a&&a!=1) upd(1,a-1,1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;V=n*m;
a.resize(n+2,vector<int>(m+2));
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j],pos[a[i][j]]={i,j};
memset(mn,0x3f,sizeof(mn));
for(int i=1;i<=V;i++)
{
point_init(i);
auto [x,y]=pos[i];
work(i,a[x+1][y],a[x][y+1],a[x+1][y+1]);
work(i,a[x-1][y],a[x][y+1],a[x-1][y+1]);
work(i,a[x+1][y],a[x][y-1],a[x+1][y-1]);
work(i,a[x-1][y],a[x][y-1],a[x-1][y-1]);
ans+=num[1];
}
cout<<ans<<'\n';
}
本文作者:spider_oyster
本文链接:https://www.cnblogs.com/spider-oyster/p/17742742.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?