9月CWOI杂题
要考试了/jk
重庆市2023年中学友谊赛Round5
A. cycle
找找规律可以发现全都合法。
证明:matrix67
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
int n=read(),ans=1;
for(int i=1;i<=n;i++)ans=ans*2%mod;
printf("%lld\n",ans);
return 0;
}
B. poker
如果你把每张牌看成一张 \((a_i,b_i)\) 的边,问题可以转化成给每条边定向,所有指到的数会取到。然后对于每个联通块,如果里面有环,那么所有的数都可以取到:具体的,可以环上的数指一圈,外面的数指从环方向上往外指。反之,则联通块是一颗树,里面有且仅有一个数取不到。容易发现应为我们要凑区间,不取的显然是最大值或最小值。于是每个树联通块找到最小最大值 \([L_i,R_i]\),如果询问区间 \([l,r]\) 完全包含了 \([L_i,R_i]\),那就取不完了。离线判断即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[2000005];
int head[1000005],tot;
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int bel[1000005],c[1000005],L[1000005],R[1000005];vector<int>p[1000005];
void dfs(int u,int rt){
if(bel[u])return;
bel[u]=rt,p[rt].push_back(u);
for(int i=head[u];i;i=e[i].nxt)dfs(e[i].v,rt);
}
int a[1000005],b[1000005],ans[1000005];pii l[1000005],Q[1000005];vector<int>v[1000005];
signed main(){
int n=read(),k=read(),m=0,t=0;
for(int i=1;i<=k;i++)a[i]=read(),b[i]=read(),add(a[i],b[i]),add(b[i],a[i]);
for(int i=1;i<=n;i++)if(!bel[i])dfs(i,++m);
for(int i=1;i<=k;i++)c[bel[a[i]]]++;
for(int i=1;i<=m;i++){
if(c[i]<(int)p[i].size()){
sort(p[i].begin(),p[i].end());
L[++t]=p[i].front(),R[t]=p[i].back();
v[R[t]].push_back(t);
}
}
int q=read();
for(int i=1;i<=q;i++)Q[i].fi=read(),Q[i].se=read(),v[Q[i].se].push_back(i+t);
int lim=-inf;
for(int i=1;i<=n;i++){
for(auto x:v[i])if(x<=t)lim=max(lim,L[x]);
for(auto x:v[i])if(x>t)ans[x-t]=((Q[x-t].fi<=lim)?0:1);
}
for(int i=1;i<=q;i++)puts((ans[i])?"Yes":"No");
return 0;
}
C. meat
四元环计数的变种。具体见 三元环(四元环)计数。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pb push_back
#define deg(u) ((int)g[u].size())
using namespace std;
const int inf=1e18,mod=1e9+7,i2=(mod+1)/2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[100005],s[100005],c[100005];vector<int>g[100005];
int cmp(int x,int y){
if(deg(x)^deg(y))return deg(x)<deg(y);
return x<y;
}
int o[100005],rk[100005];
signed main(){
int n=read(),m=read(),ans=0;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),g[u].pb(v),g[v].pb(u);
for(int i=1;i<=n;i++)o[i]=i;
sort(o+1,o+n+1,cmp);for(int i=1;i<=n;i++)rk[o[i]]=i;
for(int i=1;i<=n;i++){
for(auto j:g[i])if(rk[i]>rk[j]){
for(auto k:g[j])if(rk[i]>rk[k]){
ans=(ans+c[k]*(a[i]+a[j]+a[k])%mod+s[k])%mod;
s[k]=(s[k]+a[j])%mod,c[k]=(c[k]+1)%mod;
}
}
for(auto j:g[i])if(rk[i]>rk[j]){
for(auto k:g[j])if(rk[i]>rk[k])s[k]=c[k]=0;
}
}
printf("%lld\n",ans);
return 0;
}
D. seq
ynoi 毒瘤题,不改了。
C0302 【0905 B组】模拟测试改错
sto zjk orz
A 【1203 B组】基础计数练习题
容易发现 \(p>Q(p)\) 的 \(p\) 和 \(p<Q(p)\) 的 \(p\) 是对称的,于是我们只用求 \(p=Q(p)\) 的 \(p\) 的个数即可。容易得到递推式 \(f_n=f_{n-1}+(n-1)f_{n-2}\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int f[10000005],g[10000005];
signed main(){
int n=read(),mod=read(),i2=(mod+1)/2,ans=0;
f[1]=1,f[2]=2;for(int i=3;i<=n;i++)f[i]=(f[i-1]+1ll*f[i-2]*(i-1)%mod)%mod;
g[0]=1;for(int i=1;i<=n;i++)g[i]=1ll*g[i-1]*i%mod;
for(int i=1;i<=n;i++)ans^=1ll*(g[i]-f[i]+mod)%mod*i2%mod;
printf("%d\n",ans);
return 0;
}
B 【1203 B组】基础点分治练习题
容易发现 \(\text{lca}(u,v)\) 就是 \(u,v\) 的 LCP,\(\text{dist}(u,v)\) 就是 \(\text{popcount}(u)+\text{popcount}(v)-2\times\text{popcount}(\text{lca})\)。直接从给的点开始往上跳,算一算即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
inline void add(int &x,int y){
x+=y;if(x>=mod)x-=mod;
}
inline void sub(int &x,int y){
x-=y;if(x<0)x+=mod;
}
int jc[10000005],iv[10000005],ij[10000005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return 1ll*jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int v[1000005];
signed main(){
int n=read(),q=read();
jc[0]=1;for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=n;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=n;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod;
while(q--){
int k=read();
for(int i=1;i<=k;i++)v[i]=read();
int d=read();
if(k==0){printf("%d\n",C(n,d));continue;}
int ans=0;v[0]=-1,v[k+1]=n;
for(int i=0;i<=k;i++)add(ans,C(v[i+1],d-i)),sub(ans,C(v[i],d-i-1));
printf("%d\n",ans);
}
return 0;
}
C 【1203 B组】基础博弈练习题
思路很复杂,但是理顺了还好?
首先假设我们在第 \(i\) 行结束游戏。枚举 \(1\ldots i-1\) 行共填了 \(p\) 个数,\(i+1\ldots n\) 行共填了 \(q\) 个数。那么此时我们一共填了 \(l_i+p+q\) 个数。记 \(L=\sum\limits_{i=1}^ml_i\),\(s=l_i+p+q\),可以发现这种情况的出现概率是 \(\dfrac{1}{L^{\underline{s}}}\)。定义 \(f_{i,j}\) 表示前 \(i\) 行共填了 \(j\) 个位置(无顺序)的方案数,\(g_{i,j}\) 表示后 \(i\) 行共填了 \(j\) 个位置(无顺序)的方案数,\(h_{i,j}\) 表示前 \(i\) 个数中选 \(j\) 个,且强制第 \(i\) 个数一定被选,按任意顺序排列成的数字之和。那么有
解释一下细节问题:这里要乘上一个 \((p+q)!\) 是因为我们在计算除了 \(i\) 行之外的数时 \(f,g\) 只是选定了位置,并没有考虑填进去的顺序问题。同时,我们也可以得到 \(f,g\) 的转移式
注意这里 \(k\) 并不能取到 \(l_i\),因为我们必须保证只有在我们枚举的那一行数被填满了。预处理的时间复杂度为 \(\mathcal{O}(n^3)\)。现在问题在于如何求出 \(h\)。
直接计算是棘手的,考虑拆贡献。对于一个被填进去的数 \(s_x\),它的贡献为 \(s_x\times 10^k\),其中 \(k\) 是 \(s_x\) 右侧填的数的位数和,对每个 \(s_x\),求和它在所有可能被选到的方案中的贡献和即可。于是我们可以为 \(s_x\) 预留一个所谓的窗口,然后考虑每次填进来的数与窗口的相对顺序来转移。更具体的,可以再定义一个 \(t_{i,j,k,0/1}\) 表示在前 \(i\) 个数,选了 \(j\) 个(选进窗口也算选),我们为 \(s_x\) 预留的窗口右侧选了多少个数,目前有没有确定预留处的 \(s_x\)。有转移式
解释一下细节:这里,我们要记录这一维 \(k\) 的原因是 \(t\) 是有序的,你要考虑它插进去的具体位置。同时,转移时要乘的系数也是这个道理。算这个的复杂度也是 \(\mathcal{O}(n^3)\)。
最后再从 \(t\) 到 \(h\)。发现 \(h\) 与 \(t\) 最大的区别是强制 \(i\) 必须选,所以我们减去没选 \(i\) 的情况就好了。
总复杂度 \(\mathcal{O}(n^3)\),常数大。注意不要搞混 \(n\) 和 \(m\),\(l_i\) 和 \(|s_i|\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=90000,mod=1e9+9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int jc[90005],iv[90005],ij[90005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
int l[305],len[305],pw[90005],a[305],P[305],S[305],down[90005];char s[305][305];
int h0[305][305][305][2],h[305][305],f[305][90005],g[305][90005];
signed main(){
int n=read(),m=read(),L=0;
for(int i=1;i<=m;i++)l[i]=read(),L+=l[i];
for(int i=1;i<=n;i++)scanf("%s",s[i]+1),len[i]=strlen(s[i]+1);
for(int i=1;i<=n;i++)for(int j=1;j<=len[i];j++)a[i]=(a[i]*10%mod+s[i][j]-'0')%mod;
pw[0]=1;for(int i=1;i<=SIZ;i++)pw[i]=pw[i-1]*10%mod;
for(int i=1;i<=m;i++)P[i]=P[i-1]+l[i];
for(int i=m;i>=1;i--)S[i]=S[i+1]+l[i];
jc[0]=1;for(int i=1;i<=SIZ;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=SIZ;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=SIZ;i++)ij[i]=ij[i-1]*iv[i]%mod;
down[1]=iv[L];for(int i=2;i<=L;i++)down[i]=down[i-1]*iv[L-i+1]%mod;
f[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<=P[i];j++){
for(int k=0;k<l[i];k++){
if(j-k>=0)f[i][j]=(f[i][j]+f[i-1][j-k]*C(l[i],k)%mod)%mod;
}
}
}
g[m+1][0]=1;
for(int i=m;i>=1;i--){
for(int j=0;j<=S[i];j++){
for(int k=0;k<l[i];k++){
if(j-k>=0)g[i][j]=(g[i][j]+g[i+1][j-k]*C(l[i],k)%mod)%mod;
}
}
}
h0[0][0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=j;k++){
if(j)h0[i][j][k][0]=(h0[i][j][k][0]+h0[i-1][j-1][k][0]*(j-1-k+1)%mod)%mod;
if(j&&k)h0[i][j][k][0]=(h0[i][j][k][0]+h0[i-1][j-1][k-1][0]*pw[len[i]]%mod*(k-1+1)%mod)%mod;
if(j)h0[i][j][k][1]=(h0[i][j][k][1]+h0[i-1][j-1][k][1]*(j-1-k-1+1)%mod)%mod;
if(j&&k)h0[i][j][k][1]=(h0[i][j][k][1]+h0[i-1][j-1][k-1][1]*pw[len[i]]%mod*(k-1+1)%mod)%mod;
if(j)h0[i][j][k][1]=(h0[i][j][k][1]+h0[i-1][j-1][k][0]*a[i]%mod)%mod;
h0[i][j][k][0]=(h0[i][j][k][0]+h0[i-1][j][k][0])%mod;
h0[i][j][k][1]=(h0[i][j][k][1]+h0[i-1][j][k][1])%mod;
}
}
for(int j=0;j<=i;j++){
for(int k=0;k<=j;k++){
h[i][j]=(h[i][j]+h0[i][j][k][1])%mod;
}
}
}
int ans=0;
for(int i=1;i<=m;i++){
for(int p=0;p<=P[i-1];p++){
for(int q=0;q<=S[i+1];q++){
ans=(ans+(h[l[i]+p+q][l[i]]-h[l[i]+p+q-1][l[i]]+mod)%mod*f[i-1][p]%mod*g[i+1][q]%mod*jc[p+q]%mod*down[l[i]+p+q]%mod)%mod;
}
}
}
printf("%lld\n",ans);
return 0;
}
D 【1203 B组】基础最优化练习题
不想写了,放个 zjk 的 tj。

算了,还是补充下。就是这个问题等价于你把每个点拆成左右部点之后跑二分图权匹配,然后如果有 \(k\) 个匹配总答案就是权值和 \(+c(n-k)\)。但是在这个问题中我们可以重复经过边,所以并不是直接跑,而是先 floyed,然后每两个点连长为 \(\text{dist}(i,j)\) 的边(自己向自己不连边)。这样原来解决不了的这种情况就可以跑了。比如

这里,\(1\to 7,2\to 8\) 会有重复路径,刚刚那样做不对。

但是按上述方案连边之后就可以这样匹配上了。
此时还有一个问题,就是不一定越大的匹配越优。所以我们需要预处理每个大小的匹配的最优方案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int to,w,c,nxt;
}e[200005];
int head[605],tot=1;
void add(int u,int v,int w,int c){
e[++tot]=(edge){v,w,c,head[u]},head[u]=tot;
}
void adde(int u,int v,int w,int c){
add(u,v,w,c),add(v,u,0,-c);
}
int n,m,q,s,t,inq[605],dis[605],pre[605],lst[605],flow[605],maxflow,mincost;
int SPFA(int s,int t){
queue<int>q;q.push(s);
for(int i=1;i<=2*n+2;i++)dis[i]=flow[i]=inf,inq[i]=0;
inq[s]=1,dis[s]=0,pre[t]=-1;
while(!q.empty()){
int u=q.front();q.pop();inq[u]=0;
for(int i=head[u];i;i=e[i].nxt){
if(e[i].w>0&&dis[e[i].to]>dis[u]+e[i].c){
dis[e[i].to]=dis[u]+e[i].c;
pre[e[i].to]=u,lst[e[i].to]=i;
flow[e[i].to]=min(flow[u],e[i].w);
if(!inq[e[i].to])inq[e[i].to]=1,q.push(e[i].to);
}
}
}
return pre[t]!=-1;
}
int res[305],d[305][305];
signed main(){
n=read(),m=read(),q=read();
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j]=((i==j)?0:inf);
for(int i=1,u,v,w;i<=m;i++)u=read(),v=read(),w=read(),d[u][v]=min(d[u][v],w);
for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
s=2*n+1,t=2*n+2;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=j)adde(i,j+n,1,d[i][j]);
for(int i=1;i<=n;i++)adde(s,i,1,0),adde(i+n,t,1,0);
res[0]=mincost;int cnt=0;
while(SPFA(s,t)){
int u=t;
maxflow+=flow[t],mincost+=flow[t]*dis[t],res[++cnt]=mincost;
while(u!=s){
e[lst[u]].w-=flow[t];
e[lst[u]^1].w+=flow[t];
u=pre[u];
}
}
for(int i=1;i<=q;i++){
int c=read(),ans=inf;
for(int j=0;j<=cnt;j++)ans=min(ans,res[j]+(n-j)*c);
printf("%lld\n",ans);
}
return 0;
}
C0307 【0909 B组】模拟测试
我,是,傻,逼。
A 【0909 B组】切蛋糕
枚举行的切法,记忆化搜索即可。复杂度 \(\mathcal{O}(2^nn^3)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,a[20][20],s[20][20];
int ask(int x1,int y1,int x2,int y2){
return s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
}
int tot,p[20],f[20][20][20];
int calc(int l,int r){
int res=inf;
for(int i=2;i<=tot;i++){
res=min(res,ask(p[i-1]+1,l,p[i],r));
}
return res;
}
int dfs(int l,int r,int c){
if(c==0)return calc(l,r);
if(f[l][r][c]!=-inf)return f[l][r][c];
int res=-inf;
for(int i=l;i<r;i++){
res=max(res,min(calc(l,i),dfs(i+1,r,c-1)));
}
return f[l][r][c]=res;
}
int ans[20][20];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
a[i][j]=read(),s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
ans[i][j]=-inf;
}
}
for(int S=0;S<(1<<(n-1));S++){
int i=__builtin_popcount(S);tot=0;
p[++tot]=0;
for(int j=1;j<n;j++){
if((S>>(j-1))&1)p[++tot]=j;
}
p[++tot]=n;
for(int l=1;l<=m;l++)for(int r=l;r<=m;r++){
for(int c=0;c<=r-l;c++)f[l][r][c]=-inf;
}
for(int j=0;j<m;j++)ans[i][j]=max(ans[i][j],dfs(1,m,j));
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
printf("%d ",ans[i][j]);
}
puts("");
}
return 0;
}
B 【0909 B组】羊了个羊
发现当前 \(S\) 内有多少个元素只与我们在 \(a,b\) 中选了几个数有关,与顺序无关。于是可以把在 \(a\) 中选了 \(i\) 个,在 \(b\) 中选了 \(j\) 个时的 \(|S|+1\) 对应到点 \((i,j)\),于是问题转化为从 \((0,0)\) 走到 \((n,n)\),每次能向下或向右,让经过的所有点的点权的最大值最小。没有修改时可以 \(\mathcal{O}(n^2)\) dp。
容易发现加上修改后每次只会影响一行的点权,于是记录 \(f_{i,j}\) 表示从 \((0,0)\) 走到 \((i,j)\) 的答案,\(g_{i,j}\) 表示从 \((n,n)\) 倒着走回 \((i,j)\) 的答案,中间这一行单独处理,把两部分拼在一起即可。时间复杂度 \(\mathcal{O}(n^2)\)。卡常。
点击查看代码
#include<bits/stdc++.h>
#define int short
using namespace std;
const int inf=20000;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,a[5005],b[5005],s[5005][5005],cnt[5005],A[5005][2],B[5005][5005];
void init(){
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)B[j][i]=B[j][i-1];
B[b[i]][i]++;
}
s[0][0]=1;
for(int i=1;i<=n;++i){
s[i][0]=s[i-1][0]-cnt[a[i]]%3,cnt[a[i]]++;
s[i][0]+=cnt[a[i]]%3;A[i][0]=cnt[a[i]],A[i][1]=cnt[a[i+1]];
}
for(int i=1;i<=n;++i)cnt[i]=0;
for(int j=1;j<=n;++j){
s[0][j]=s[0][j-1]-cnt[b[j]]%3,cnt[b[j]]++,s[0][j]+=cnt[b[j]]%3;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)cnt[j]=0;
for(int j=1;j<=i;++j)cnt[a[j]]++;
for(int j=1;j<=n;++j)s[i][j]=s[i][j-1]-cnt[b[j]]%3,cnt[b[j]]++,s[i][j]+=cnt[b[j]]%3;
}
}
int f[5005][5005],g[5005][5005],ns[5005],nf[5005];
signed main(){
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i)b[i]=read();
init();f[0][0]=s[0][0];g[n][n]=s[n][n];
for(int i=0;i<=n;++i)for(int j=0;j<=n;++j){
if(i==0&&j==0)continue;
int tmp=inf;
if(i>0)tmp=min(tmp,f[i-1][j]);
if(j>0)tmp=min(tmp,f[i][j-1]);
f[i][j]=max(tmp,s[i][j]);
}
for(int i=n;i>=0;--i)for(int j=n;j>=0;--j){
if(i==n&&j==n)continue;
int tmp=inf;
if(i<n)tmp=min(tmp,g[i+1][j]);
if(j<n)tmp=min(tmp,g[i][j+1]);
g[i][j]=max(tmp,s[i][j]);
}
for(int i=1;i<n;++i){
int ans=inf;
for(int j=0;j<=n;++j){
ns[j]=s[i][j];
if(a[i]!=a[i+1]){
ns[j]-=(A[i][0]+B[a[i]][j])%3;ns[j]+=(A[i][0]+B[a[i]][j]+2)%3;
ns[j]-=(A[i][1]+B[a[i+1]][j])%3;ns[j]+=(A[i][1]+B[a[i+1]][j]+1)%3;
}
}
nf[0]=max(f[i-1][0],ns[0]);ans=min(ans,max(nf[0],g[i+1][0]));
for(int j=1;j<=n;++j)nf[j]=max(min(nf[j-1],f[i-1][j]),ns[j]),ans=min(ans,max(nf[j],g[i+1][j]));
printf("%d\n",ans);
}
return 0;
}
C0309 【0913 B组】模拟测试
青蛙,青蛙,青蛙题。
A 【1124 B组】水仙花
考虑容斥。这个限制条件有一个很好的性质:当 \(a_i,a_{i+1}\) 不合法时,\(a_i\) 至少是 \(a_{i+1}\) 的两倍,故不合法的一段的长度在 \(\mathcal{O}(\log m)\) 级别。定义 \(s_{i}\) 表示长为 \(i\) 的不合法序列个数,\(g_{i}\) 表示长为 \(i\) 的所有序列的贡献。然后转移的时候枚举极长不合法段的长度转移即可。时间复杂度 \(\mathcal{O}((m\ln m+n)\log m)\),前面是预处理的复杂度。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int f[25][100005],g[100005],s[100005];
signed main(){
int n=read(),m=read(),lim=(int)log2(m)+1;
for(int i=1;i<=m;i++)f[1][i]=1;
for(int i=1;i<=lim;i++)for(int j=1;j<=m;j++){
for(int k=2*j;k<=m;k+=j)f[i+1][k]=(f[i+1][k]+f[i][j])%mod;
}
for(int i=1;i<=lim;i++)for(int j=1;j<=m;j++)s[i]=(s[i]+f[i][j])%mod;
g[0]=1;
for(int i=1;i<=n;i++){
g[i]=g[i-1]*m%mod;
for(int j=2;j<=min(i,lim);j++){
int o=((j&1ll)?-1:1);
g[i]=(g[i]-o*g[i-j]*s[j]%mod+mod)%mod;
}
}
printf("%lld",g[n]);
return 0;
}
B 【1124 B组】勿忘我
神奇题。考虑按顺序一个一个插进去。那么在插入 \(u\) 的时候,如果前 \(u-1\) 个点中没有分界点,我们直接插到末尾;如果有,由于最多只有一个分界点,我们记分界点为 \(x\),\(x\) 的前驱和后继为 \(a\) 和 \(b\),如下图

当 \((a,x)=(u,x)\) 时,我们把 \(u\) 插入 \((x,b)\) 间,反之插入 \((a,x)\) 间。这样做有什么好处?我们惊喜的发现,这种构造方式一定合法,且插入完成后分界点要么消失,要么只会在 \(a,x,b,u\) 中一个。于是链表维护,复杂度 \(\mathcal{O}(n\log n)\),瓶颈在判断两点之间是否有边。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int pre[300005],nxt[300005];unordered_map<int,int>g[300005];
signed main(){
int n=read(),m=read(),pos=0;
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),g[u][v]=g[v][u]=1;
pre[1]=0,nxt[1]=2,pre[2]=1,nxt[2]=0;
int tail=2;
for(int i=3;i<=n;i++){
if(pos==1||pos==tail)pos=0;
if(pos==0){
if(g[tail][i]!=g[pre[tail]][tail])pos=tail;
nxt[tail]=i,pre[i]=tail,nxt[i]=0,tail=i;
}
else{
int a=pre[pos],b=nxt[pos];
if(g[pos][i]==g[a][pos]){
pre[i]=pos,nxt[i]=b,nxt[pos]=i,pre[b]=i;
if(g[i][b]==g[a][pos])pos=b;
else pos=i;
}
else{
pre[i]=a,nxt[i]=pos,nxt[a]=i,pre[pos]=i;
if(g[a][i]==g[a][pos])pos=i;
else pos=a;
}
}
}
for(int i=1;i;i=nxt[i])printf("%d ",i);
return 0;
}
C 【1124 B组】君子兰
定义 \(f_{i,j,k}\) 表示如果在 \(a\) 中选择 \(i\) 个数,且每个数不超过 \(j\) 的前提下,\(a''_{k}\) 的期望是多少。容易发现 \(a\) 中每个数不超过 \(j\) 的限制完全等价于 \(a'\) 中所有数的和不超过 \(j\)。转移时不妨枚举 \(a'\) 内为 1 的元素有 \(x\) 个,此时我们可以把 \(a'\) 中所有数全都减一,再去掉已经变成 0 的数,问题就被转化为了 \(f_{i-x,j-i,k-x}\)。时间复杂度 \(\mathcal{O}(n^3m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
db f[55][10005][55],C[10005][55];
signed main(){
int n=read(),m=read();
for(int i=0;i<=m;i++){
C[i][0]=1;
for(int j=1;j<=min(i,n);j++)C[i][j]=C[i-1][j-1]+C[i-1][j];
}
for(int i=1;i<=m;i++)f[1][i][1]=(i+1)/2.0;
for(int i=2;i<=n;i++)for(int j=i;j<=m;j++)for(int k=1;k<=i;k++){
for(int x=0;x<=k;x++){
f[i][j][k]+=C[i][x]*C[j-i][i-x]*f[i-x][j-i][k-x];
}
f[i][j][k]=f[i][j][k]/C[j][i]+k;
}
for(int i=1;i<=n;i++)printf("%.7lf ",f[n][m][i]);
return 0;
}
D 【1124 B组】郁金香
考虑丢到 trie 上思考问题。定义 \(f_{u,x}\) 表示目前考虑到 \(k\) 的第 \(x\) 位,在 \(u\) 的子树内选数的最大方案。但是这样有问题:如果第 \(x\) 位为 0 我们只能选一边,但 \(x\) 位为 1 时并没有限制不同子树间的方法,这个方法寄了!
于是拓展一下,定义 \(f_{u,v,x}\) 表示目前考虑到 \(k\) 的第 \(x\) 位,在 \(u\) 和 \(v\) 的子树内选数的最大方案。这个做法一眼看上去也是寄的,因为我们发现再讨论一下可能就是好多个点互相限制。赛时我想到这里后直接没往下想。但是!如果你耐心去讨论一下,你会发现惊喜:
-
若 \(k\) 的第 \(x\) 位为 0,我们可以只在 \(u\) 或 \(v\) 的子树内任意选,也可以在 \(ls(u),ls(v)\) 或 \(rs(u),rs(v)\) 中选;
-
若 \(k\) 的第 \(x\) 位为 1,在 \(ls(u)\) 和 \(ls(v)\),\(rs(u)\) 和 \(rs(v)\) 之间是没有限制的,所以我们只用把在 \(ls(u),rs(v)\) 中选的方案和在 \(rs(u),ls(v)\) 中选的方案加在一起。
这样做是对的!时间复杂度是总点数级别,\(\mathcal{O}(n\log V)\)。实现中需要注意一些细节。
一道相似的题:CF1616H. Keep XOR Low。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ls(u) (son[u][0])
#define rs(u) (son[u][1])
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int tot,cnt[2000005],son[2000005][2];
void init(){
for(int i=1;i<=tot;i++)son[i][0]=son[i][1]=cnt[i]=0;
tot=1;
}
void insert(int v){
int u=1;
for(int i=20;i>=0;i--){
int o=(v>>i)&1;
if(!son[u][o])son[u][o]=++tot;
cnt[u]++,u=son[u][o];
}
cnt[u]++;
}
int n,k,a[200005];
int dfs(int u1,int u2,int x){
if(!u1)return cnt[u2];
if(!u2)return cnt[u1];
int o=(k>>x)&1,res=-inf;
if(u1==u2){
if(x==-1)return cnt[u1];
if(o==1)res=max(res,dfs(ls(u1),rs(u1),x-1));
else res=max(res,max(dfs(ls(u1),ls(u1),x-1),dfs(rs(u1),rs(u1),x-1)));
return res;
}
if(x==-1)return cnt[u1]+cnt[u2];
if(o==1)res=max(res,dfs(ls(u1),rs(u2),x-1)+dfs(rs(u1),ls(u2),x-1));
else res=max(res,max(max(cnt[u1],cnt[u2]),max(dfs(ls(u1),ls(u2),x-1),dfs(rs(u1),rs(u2),x-1))));
return res;
}
signed main(){
int T=read();
while(T--){
n=read(),k=read();init();
for(int i=1;i<=n;i++)a[i]=read(),insert(a[i]);
printf("%d\n",dfs(1,1,20));
}
return 0;
}
C0311 【0915 B组】模拟测试
变成练习赛了,题目很难,只会 T1。
A 【0915 B组】以撒
首先 \(\mathcal{O}(n\log n\log l)\) 的做法是简单的,你考虑二分答案 \(t\),然后每只自狙蝇 \((x_i,y_i)\) 会限制人不能在 \([x_i-\sqrt{t^2-y_i^2},x_i+\sqrt{t^2-y_i^2}]\) 这段区间内,然后你算出所有区间判断它们是否包含了整个线段即可。这里需要一个排序,所以复杂度带 \(\log n\)。
可以优化吗?当然。我们发现判断的过程并不需要这么复杂,我们只关心这些区间的并是否会完全覆盖 \([0,l]\),所以只用看最靠前的那段区间并。如图:

也就是说,我们只关心包含了 0 的那段区间并有没有包含 \(l\)。所以如果在二分前就把把所有点用 \(x_i\) 当第一关键字,\(y_i\) 当第二关键字从小到大排序,在 check 的时候就不用排序了。正确性显然。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define db double
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
const db eps=1e-6;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int calc(db x,db y){
if(fabs(x-y)<=eps)return 0;
return ((x-y>0)?1:-1);
}
int n,L,x[1000005],y[1000005];
struct Node{
db l,r;
}p[1000005];
int cmp(Node x,Node y){
if(calc(x.l,y.l)==0)return (calc(x.r,y.r)<=0);
else return (calc(x.l,y.l)<=0);
}
int check(db mid){
db pos=0.0;
for(int i=1;i<=n;i++){
if(mid*mid-y[i]*y[i]<0.0)continue;
db v=sqrt(mid*mid-y[i]*y[i]);
if(calc(pos,x[i]-v)>=0)pos=max(pos,x[i]+v);
}
return (calc(pos,L*1.0)<0);
}
pii o[1000005];
signed main(){
n=read(),L=read();
for(int i=1;i<=n;i++)o[i].fi=read(),o[i].se=read();
sort(o+1,o+n+1);
for(int i=1;i<=n;i++)x[i]=o[i].fi,y[i]=o[i].se;
db l=0,r=2250000000,res=l;
for(int i=1;i<=50;i++){
db mid=(l+r)/2.0;
if(check(mid))res=mid,l=mid;
else r=mid;
}
printf("%.5lf\n",res);
return 0;
}
C0314 【0923 B组】模拟测试
不会高维前缀和导致的。
A 【0923 B组】糗大了
签到。倒着看删点的过程就变成加点了,并查集维护。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,res,a[100005],p[100005],tag[100005],ans[100005],fa[100005],val[100005];
int find(int x){
return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
fa[y]=x,val[x]+=val[y],res=max(res,val[x]);
}
vector<int>g[100005];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),g[u].push_back(v),g[v].push_back(u);
for(int i=1;i<=n;i++)p[i]=read();
for(int i=n;i>=1;i--){
ans[i]=res;
fa[p[i]]=p[i],val[p[i]]=a[p[i]],tag[p[i]]=1,res=max(res,val[p[i]]);
for(auto x:g[p[i]])if(tag[x])merge(p[i],x);
}
for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
return 0;
}
B 【0923 B组】快哭了
找找规律可以发现 \((i,j)\) 会对 \(k\) 产生贡献当且仅当 \(k\&i=i\) 且 \(k\&j=j\),即 \(k\&(i|j)=(i|j)\)。考虑 SOSDP,即高维前缀和。具体地,定义 \(w_{t}\) 表示 \((i|j)=t\) 的所有 \(a_{i,j}\) 的异或和,\(f_{i,j}\) 表示 \(j\) 的所有子集中,只有低 \(i\) 位可能变化的子集和。
这个定义可能有点绕,大概就是 \(f(2,(10011)_2)=w((10011)_2)+w((10010)_2)+w((10001)_2)+w((10000)_2)\)。转移就是若 \(bit(j,i)=0\),则 \(f_{i,j}=f_{i-1,j}\),反之 \(f_{i,j}=f_{i-1,j}+f_{i-1,j\oplus2^i}\)。显然这可以滚掉一维,令 \(s=\max\{n,m\}\),时间复杂度 \(\mathcal{O}(nm+s\log s)\),空间复杂度 \(\mathcal{O}(s)\),查询 \(\mathcal{O}(1)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int w[(1<<23)+5],f[(1<<23)+5];
void init(int n, int m, int q, int aw, int kw, const vector<vector<int> > &a) {
for(int i=0;i<n;i++)for(int j=0;j<m;j++)w[i|j]^=a[i][j];
for(int i=0;i<(1<<23);i++)f[i]=w[i];
for(int i=0;i<23;i++)for(int j=0;j<(1<<23);j++)if((j>>i)&1)f[j]^=f[j^(1<<i)];
}
int query(int k) {
return f[k&((1<<23)-1)];
}
struct xorShift128Plus {
unsigned long long k1, k2;
unsigned long long gen() {
register unsigned long long k3 = k1, k4 = k2;
k1 = k4;
k3 ^= k3 << 23;
k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
return k2 + k4;
}
int gen(int w) {
return gen() >> (64 - w);
}
};
int main() {
int n, m, q, aw, kw;
xorShift128Plus rng;
scanf("%d%d%d%d%d%llu%llu", &n, &m, &q, &aw, &kw, &rng.k1, &rng.k2);
vector<vector<int> > a(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
a[i][j] = rng.gen(aw);
}
}
init(n, m, q, aw, kw, a);
unsigned long long res = 0;
for (int i = 1; i <= q; i++) {
res ^= (unsigned long long)i * query(rng.gen(kw));
}
printf("%llu\n", res);
return 0;
}
C 【0923 B组】色
显然至多存在一个交集为空的集合,因为如果有很多个你可以把他们合并,答案一定变优。分情况讨论一下:如果存在交集为空的集合,那么答案就是所有线段中最长的 \(k-1\) 的长度和。
如果全都不为空,我们会发现若存在区间 \([l_i,r_i]\) 和 \([l_j,r_j]\) 满足 \(l_j\le l_i\land r_i\le r_j\),那么 \([l_j,r_j]\) 要么单独占一个集合要么和 \([l_i,r_i]\) 放一起,证明就是如果 \(j\) 所在的集合大小超过 1,那么你把它拿出来跟 \(i\) 放一起一定不劣。所以我们把这些区间去掉之后,剩下的区间一定是形如这样的:

可以证明,我们每次选连续的一些线段分到一个集合里一定最优。证明就是你考虑如果在 \(x\) 到 \(y\) 这几条线段中有一个 \(p\) 被 \(x\) 之前/\(y\) 之后的一个集合包含了,那么我把 \(p\) 换成 \(x\) 或 \(y\) 一定不劣。令每个集合的分割点为 \(s_i\),即剩下的 \(m\) 条线段分成 \(\{1,\ldots s_1\},\{s_1+1,\ldots s_2\},\ldots\{s_t+1,\ldots m\}\) 这几个集合,那么总代价就是 \((r_1-l_{s_1})+(r_{s_t+1}-l_m)+\sum\limits_{i=2}^t r_{s_{i-1}+1}-l_{s_i}=r_1-l_m+\sum\limits_{i=1}^tr_{s_i+1}-l_{s_i}\),那么我们直接把保留线段的 \(r_{i+1}-l_i\)(不算第 \(m\) 条,因为这之后没有线段了)和被删除线段的 \(r_i-l_i\) 丢到一起排序即可。复杂度 \(\mathcal{O}(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Node{
int l,r;
}a[5005],b[5005],c[5005];
int cmp1(int x,int y){
return x>y;
}
int cmp2(Node x,Node y){
if(x.l^y.l)return x.l>y.l;
return x.r<y.r;
}
int cmp3(Node x,Node y){
if(x.l^y.l)return x.l<y.l;
return x.r<y.r;
}
int siz[5005],del[5005];
signed main(){
int n=read(),k=read(),m=0,ans=0,sum=0,tot=0;
for(int i=1;i<=n;i++)a[i].l=read(),a[i].r=read(),siz[i]=a[i].r-a[i].l;
sort(siz+1,siz+n+1,cmp1);sort(a+1,a+n+1,cmp2);
for(int i=1;i<=k-1;i++)ans+=siz[i];
for(int i=1;i<=n;i++){
int j=i+1;
while(j<=n&&a[j].l<=a[i].l&&a[j].r>=a[i].r)del[j]=1,j++;
i=j-1;
}
for(int i=1;i<=n;i++)if(!del[i])b[++m]=a[i];
sort(b+1,b+m+1,cmp3);
for(int i=1;i<m;i++)siz[++tot]=b[i+1].r-b[i].l;
for(int i=1;i<=n;i++)if(del[i])siz[++tot]=a[i].r-a[i].l;
sort(siz+1,siz+tot+1,cmp1);
for(int i=1;i<k;i++)sum+=siz[i];
printf("%lld\n",max(ans,sum+b[1].r-b[m].l));
return 0;
}
D 【0923 B组】发怒
定义 \(f_{i,j}\) 表示在 \(i\) 的子树内选一个包含 \(i\) 的连通块,且乘积为 \(j\) 的方案数,直接做可以 \(\mathcal{O}(nm\ln m)\)。但是你发现有值的 \(j\) 并不多,所以可以用一个 map 存有值的位置。你发现这样做跑得飞快,能得 55 分。当然,这不是正解。
考虑点分治。令当前分治中心为 \(r\),那么新定义 \(f_{i,j}\) 表示在以 \(r\) 为根的子树内考虑了 dfs 序在 \([1,i)\) 之间的点,乘积为 \(j\) 的方案数,\(u\) 表示 dfs 序为 \(i\) 的点。如果选 \(a_u\) 那么我们转移到 \(f_{i+1,j\times a_u}\),反之我们需要跳过 \(u\) 的子树,转移到 \(f_{i+siz_u,j}\)。那么复杂度为 \(\mathcal{O}(nm\log n)\)。
可以发现,我们其实并不关心 \(j\) 是多少,只关心 \(j\) 还能乘上多少,于是可以把 \(\lfloor\dfrac{m}{j}\rfloor\) 相同的当成一样的状态。复杂度 \(\mathcal{O}(n\sqrt{m}\log n)\)。
有一说一,这个把树形 dp 化成在 dfs 序上 dp 的想法还是很常见的。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void inc(int &x,int y){
x+=y;if(x>=mod)x-=mod;
}
struct edge{
int v,nxt;
}e[4005];
int tot,head[2005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int root,siz[2005],ma[2005],vis[2005];
void dfsroot(int u,int fa,int sum){
siz[u]=1,ma[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dfsroot(v,u,sum);siz[u]+=siz[v],ma[u]=max(ma[u],siz[v]);
}
ma[u]=max(ma[u],sum-siz[u]);
if(ma[u]<ma[root])root=u;
}
int cur,dfn[2005],rnk[2005],s[2005];
void dfsdis(int u,int fa){
dfn[u]=++cur,rnk[cur]=u,s[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||vis[v])continue;
dfsdis(v,u);s[u]+=s[v];
}
}
int n,m,ans,all,val[2005],id[1000005],a[2005],f[2005][2005];
void calc(int u){
cur=0,dfn[u]=++cur,rnk[cur]=u,s[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(vis[v])continue;
dfsdis(v,u);s[u]+=s[v];
}
for(int i=1;i<=cur+1;i++)for(int j=1;j<=all;j++)f[i][j]=0;
f[1][id[m]]=1;
for(int i=1;i<=cur;i++){
for(int j=1;j<=all;j++){
inc(f[i+1][id[val[j]/a[rnk[i]]]],f[i][j]);
inc(f[i+s[rnk[i]]][j],f[i][j]);
}
}
inc(ans,mod-1);//empty set
for(int i=1;i<=all;i++)inc(ans,f[cur+1][i]);
}
void solve(int u){
vis[u]=1,calc(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;if(vis[v])continue;
root=0,dfsroot(v,0,siz[v]),solve(root);
}
}
signed main(){
n=read(),m=read();
for(int l=1,r;l<=m;l=r+1)r=((m/l==0)?m:m/(m/l)),val[++all]=m/l,id[m/l]=all;
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<n;i++)u=read(),v=read(),add(u,v),add(v,u);
ma[0]=n+5;root=1;dfsroot(1,0,n);solve(root);
printf("%d\n",ans);
return 0;
}
C0318 【0926 B组】模拟测试
只会 T1T2,T3 一分都没写,被吊打。
A 【0926 B组】学飞机
因为题目中都是非负整数,所以如果每一维单独排序后,每次每一维上满足 \(p_j\ge r_{i,j}\) 的都是一段前缀,像 bfs 一样拓展即可。复杂度 \(\mathcal{O}(nk)\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Node{
ll val;int id;
};
int cmp(Node x,Node y){
return x.val<y.val;
}
int cnt[1000005],pos[1000005];ll now[1000005];
signed main(){
int n=read(),k=read(),ans=0;
vector<vector<Node> >a;a.resize(k+5);
vector<vector<int> >b;b.resize(n+5);
for(int i=0;i<n;i++)for(int j=0;j<k;j++)a[j].push_back((Node){read(),i});
for(int i=0;i<n;i++)for(int j=0;j<k;j++)b[i].push_back(read());
for(int i=0;i<k;i++)sort(a[i].begin(),a[i].end(),cmp);
queue<int>q;
for(int i=0;i<k;i++){
pos[i]=-1;
while(pos[i]+1<n&&a[i][pos[i]+1].val<=now[i]){
pos[i]++,cnt[a[i][pos[i]].id]++;
if(cnt[a[i][pos[i]].id]==k)q.push(a[i][pos[i]].id);
}
}
while(!q.empty()){
int u=q.front();q.pop();ans++;
for(int i=0;i<k;i++){
now[i]+=b[u][i];
while(pos[i]+1<n&&a[i][pos[i]+1].val<=now[i]){
pos[i]++,cnt[a[i][pos[i]].id]++;
if(cnt[a[i][pos[i]].id]==k)q.push(a[i][pos[i]].id);
}
}
}
printf("%d\n",ans);
return 0;
}
B 【0926 B组】造飞机
挺板的颜色段均摊。你发现如果 \([l_i,r_i]\) 有一部分机器上一次使用是在 \([l_j,r_j]\) 这里,那么那一段每台对应的机器经过的时间都是相同的,询问离线下来加一下即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct Node{
int l,r,c;
bool operator <(const Node &b)const{
if(l^b.l)return l<b.l;
return r<b.r;
}
};
set<Node>s;
int n,m,q,l[200005],r[200005],sum[200005],qu[200005];
int dist(int i,int j,int p){
return (r[i]-p)+(sum[j-1]-sum[i])+(p-l[j]);
}
void split(int p){
auto it=prev(s.upper_bound((Node){p,inf,0}));
int l=it->l,r=it->r,c=it->c;s.erase(it);
if(l<=p)s.insert((Node){l,p,c});
if(p+1<=r)s.insert((Node){p+1,r,c});
}
int tot,c[200005],b[200005];
void add(int val,int cnt){
int pos=upper_bound(b+1,b+tot+1,val)-b-1;
if(pos<1||pos>tot||b[pos]>val)return;
c[1]+=cnt,c[pos+1]-=cnt;
}
signed main(){
n=read(),m=read(),q=read(),tot=0;
for(int i=1;i<=m;i++)l[i]=read(),r[i]=read(),sum[i]=sum[i-1]+r[i]-l[i]+1;
for(int i=1;i<=q;i++)qu[i]=b[++tot]=read();
sort(b+1,b+tot+1);tot=unique(b+1,b+tot+1)-b-1;
s.insert((Node){0,n+1,0});
for(int i=1;i<=m;i++){
split(l[i]-1),split(r[i]);
auto itl=s.lower_bound((Node){l[i],-inf,0});
auto itr=s.lower_bound((Node){r[i]+1,-inf,0});
for(auto it=itl;it!=itr;it++){
int vl=it->l,vr=it->r,vc=it->c;
if(vc)add(dist(vc,i,vl),vr-vl+1);
}
s.erase(itl,itr);s.insert((Node){l[i],r[i],i});
}
for(int i=1;i<=tot;i++)c[i]+=c[i-1];
for(int i=1;i<=q;i++)printf("%lld ",c[lower_bound(b+1,b+tot+1,qu[i])-b]);
return 0;
}
C 【0926 B组】开飞机
容易发现飞机最优走法是严格上升一段,高度不变一段,然后严格下降一段。严格下降可以看做从 \(n\) 开始严格上升,所以只看严格上升怎么处理。因为一直在上升,假设当前时间为 \(t\),那高度应该也是 \(t\)。所以在 dijkstra 的基础上把 d[v]=d[u]+1 改成 d[v]=max(d[u]+1,a[v]) 即可。
进一步的,可以发现高度不变的持续时间不会超过 1,否则我们可以延长严格上升和高度严格下降的时长,使高度不变的时间减少 2。所以答案就是 \(\min\{\min\limits_{i=1}^n\{2\times\max\{d_{1,i},d_{n,i}\}\},\min\limits_{(i,j)\in E}\{2\times\max\{d_{1,i},d_{n,j}\}+1\}\}\)。
点击查看代码
#include<bits/stdc++.h>
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,nxt;
}e[800005];
int tot,head[200005];
void add(int u,int v){
e[++tot]=(edge){v,head[u]},head[u]=tot;
}
int n,m,ans=inf,d[200005],vis[200005],a[200005],D[2][200005];
void dijkstra(int s,int ty){
for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
priority_queue<pii,vector<pii>,greater<pii> >q;
d[s]=0;q.push(mk(d[s],s));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(d[v]>max(a[v],d[u]+1))d[v]=max(a[v],d[u]+1),q.push(mk(d[v],v));
}
}
for(int i=1;i<=n;i++)D[ty][i]=d[i];
}
signed main(){
n=read(),m=read();for(int i=1;i<=n;i++)a[i]=read();
for(int i=1,u,v;i<=m;i++)u=read(),v=read(),add(u,v),add(v,u);
dijkstra(1,0);dijkstra(n,1);for(int i=1;i<=n;i++)ans=min(ans,max(D[0][i],D[1][i])*2);
for(int i=1;i<=n;i++)for(int j=head[i];j;j=e[j].nxt)ans=min(ans,max(D[0][i],D[1][e[j].v])*2+1);
printf("%d\n",ans);
return 0;
}
D 【0926 B组】隐身飞机
怎么做了这么多区间题还不会啊。
发现能选出若干个区间使得它们的并为询问区间 \([ql,qr]\) 的充要条件是 \(\forall i\in[ql,qr]\),都存在一个区间 \([l,r]\) 满足 \(i\in[l,r]\land ql\le l\land r\le qr\)。于是把询问离线下来,从左往右枚举右端点 \(r\),每个点维护在包含它的且右端点 \(\le r\) 的所有区间中最大的左端点,那么判断条件就是 \([ql,qr]\) 的区间最小值是否 \(\ge ql\)。需要区间取 \(\max\),区间查询 \(\min\),上一个 Seg-beats 维护。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct segtree{
#define ls p<<1
#define rs p<<1|1
#define lson l,mid,ls
#define rson mid+1,r,rs
struct Node{
int mi,se,tag;
}c[2000005];
void pushup(int p){
if(c[ls].mi==c[rs].mi)c[p].mi=c[ls].mi,c[p].se=max(c[ls].se,c[rs].se);
else if(c[ls].mi<c[rs].mi)c[p].mi=c[ls].mi,c[p].se=min(c[ls].se,c[rs].mi);
else c[p].mi=c[rs].mi,c[p].se=min(c[ls].mi,c[rs].se);
}
void pushdown(int l,int r,int p){
if(!c[p].tag)return;
int siz=r-l+1,ln=siz-(siz>>1),rn=siz>>1;
c[ls].mi=c[p].tag,c[rs].mi=c[p].tag;
c[ls].tag=c[p].tag,c[rs].tag=c[p].tag;
c[p].tag=0;
}
void build(int l,int r,int p){
c[p].tag=0;
if(l==r){c[p].mi=0,c[p].se=-inf;return;}
int mid=(l+r)>>1;
build(lson),build(rson);
pushup(p);
}
void update(int l,int r,int p,int L,int R,int k){
if(c[p].mi>=k)return;
if(l==r){c[p].mi=max(c[p].mi,k);return;}
if(L<=l&&r<=R&&c[p].mi<k&&c[p].se>=k){c[p].mi=k,c[p].tag=k;return;}
int mid=(l+r)>>1;pushdown(l,r,p);
if(L<=mid)update(lson,L,R,k);
if(R>mid)update(rson,L,R,k);
pushup(p);
}
int query(int l,int r,int p,int L,int R){
if(L<=l&&r<=R)return c[p].mi;
int mid=(l+r)>>1,res=inf;pushdown(l,r,p);
if(L<=mid)res=min(res,query(lson,L,R));
if(R>mid)res=min(res,query(rson,L,R));
return res;
}
#undef ls
#undef rs
#undef lson
#undef rson
}Tr;
int l[500005],r[500005],ql[500005],qr[500005],ans[500005];vector<int>t[500005],v[500005];
signed main(){
int n=read(),m=read(),q=read();
for(int i=1;i<=m;i++)l[i]=read(),r[i]=read(),t[r[i]].push_back(i);
for(int i=1;i<=q;i++)ql[i]=read(),qr[i]=read(),v[qr[i]].push_back(i);
Tr.build(1,n,1);
for(int i=1;i<=n;i++){
for(auto x:t[i])Tr.update(1,n,1,l[x],r[x],l[x]);
for(auto x:v[i])ans[x]=(Tr.query(1,n,1,ql[x],qr[x])>=ql[x]);
}
for(int i=1;i<=q;i++)puts(ans[i]?"YES":"NO");
return 0;
}
C0319 【0928 B组】模拟测试
好水的数据。
A 【1122 B组】小黄鸭与矩阵
这个玩意是有图论意义的:把这个矩阵看作是一张图上的邻接矩阵,初始矩阵表示 \(i\to j\) 有没有一条边,最终矩阵表示 \(i\to j\) 有没有一条长恰好为 \(k\) 的路径。显然这个图不能有环。贪心考虑可以把 \(n\) 个点分成 \(k\) 组,每组间相互连边,属于同一组的点间不连边。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
int n=read(),m=read(),siz=n/m,lft=n%m;
printf("%lld\n",n*(n-1)/2-(m-lft)*siz*(siz-1)/2-lft*(siz+1)*siz/2);
return 0;
}
B 【1122 B组】小黄鸭与最短路
因为数据很水,所以这个玄学做法也能过。
直接跑 \(q\) 遍 dij 的暴力你是会的,然后我们在这个基础上加一个优化,如果此时 \(1\) 和 \(n\) 连通,我们才跑 dij,然后他就过了!而且很快。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[600005];
int tot,head[50005];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,q,T,d[50005],vis[50005];
void dijkstra(int s){
for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
priority_queue<pii,vector<pii>,greater<pii> >q;
d[s]=0;q.push(mk(d[s],s));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(d[v]>d[u]+w)d[v]=d[u]+w,q.push(mk(d[v],v));
}
}
}
int fa[50005];
int find(int x){
return ((x==fa[x])?x:fa[x]=find(fa[x]));
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x!=y)fa[x]=y;
}
signed main(){
n=read(),q=read(),T=read();
for(int i=1;i<=n;i++)fa[i]=i;
vector<int>ans;
for(int _=1;_<=q;_++){
int u=read(),v=read(),w=read();
add(u,v,w),add(v,u,w);merge(u,v);
if(find(1)!=find(n))continue;
dijkstra(1);
if(d[n]<=T){
ans.push_back(_);
tot=0;for(int i=1;i<=n;i++)head[i]=0,fa[i]=i;
}
}
printf("%lld\n",(int)ans.size());
for(auto x:ans)printf("%lld ",x);
return 0;
}
下面是正解:直接跑 \(q\) 遍 dij 显然太多了。假设当前从第 \(s\) 条边开始,我们考虑二分求最小的 \(t\),使得加入编号为 \([s,t]\) 的边时整张图会被清空。在二分的时候,我们只把当前这张图上会涉及的点拿出来跑 dij,这样复杂度就只与图中的边数有关了。当清空次数很少的时候这样是很快的,可以通过特殊性质。但是如果每条边都会清空的话,复杂度就退化到了 \(\mathcal{O}(nq\log^2q)\),甚至不如暴力。
为什么二分这么慢?原因是我们一次二分时很可能会一下子尝试一个很大的答案,这就拖慢了二分的速度,考虑倍增优化。具体地,假设 \([s,s+2^k)\) 不会让整张图清空而 \([s,s+2^{k+1})\) 会,说明最小的 \(t\) 就在 \([s+2^k,s+2^{k+1})\) 中,我们在这个范围内二分即可。此时复杂度优化到了 \(\mathcal{O}(q\log^2q)\),可以通过。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int>pii;
const int inf=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct edge{
int v,w,nxt;
}e[600005];
int tot,head[50005];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
int n,q,T,d[50005],vis[50005],eu[300005],ev[300005],ew[300005];
int dijkstra(int l,int r){
for(int i=l;i<=r;i++)add(eu[i],ev[i],ew[i]),add(ev[i],eu[i],ew[i]);
priority_queue<pii,vector<pii>,greater<pii> >q;
vis[1]=0,d[1]=0;q.push(mk(d[1],1));
while(!q.empty()){
int u=q.top().se;q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w;
if(d[v]>d[u]+w)d[v]=d[u]+w,q.push(mk(d[v],v));
}
}
int res=d[n];
tot=0;for(int i=l;i<=r;i++)d[eu[i]]=d[ev[i]]=inf,vis[eu[i]]=vis[ev[i]]=0,head[eu[i]]=head[ev[i]]=0;
return res;
}
signed main(){
n=read(),q=read(),T=read();vector<int>ans;int p=1;
for(int i=1;i<=n;i++)d[i]=inf,vis[i]=0;
for(int i=1;i<=q;i++)eu[i]=read(),ev[i]=read(),ew[i]=read();
while(p<=q){
int t=1,flag=1;
while(flag){
if(dijkstra(p,min(q,p+t-1))<=T)break;
else if(p+t-1>q){flag=0;break;}
t<<=1;
}
if(!flag)break;
int l=p+t/2,r=p+t-1,res=-1;
while(l<=r){
int mid=(l+r)>>1;
if(dijkstra(p,mid)<=T)res=mid,r=mid-1;
else l=mid+1;
}
if(res==-1)break;
ans.push_back(res),p=res+1;
}
printf("%lld\n",(int)ans.size());
for(auto x:ans)printf("%lld ",x);
return 0;
}
C 【1122 B组】小黄鸭与涂色画
agc033d,但是加强版。
沿用那个做法,定义 \(f_{x1,y1,y2,k}\) 表示左上角在 \((x1,y1)\),右下角纵坐标为 \(y2\),复杂度至多为 \(k\),能延伸到哪里。横着切就是两个 \(k-1\) 拼起来,很简单,重点在竖着切。如果我竖着从 \(p\) 处切开,那么答案就是 \(\min\{f_{x1,y1,p,k-1},f_{x1,p+1,y2,k-1}\}\)。这个玩意显然随着 \(p\) 的增大,左边会越来越小,右边会越来越大。而随着 \(y2\) 的增大,右边也会增大。所以这玩意算是个单峰函数,而且随着 \(y2\) 增大峰顶逐渐变大,我们只需要维护峰顶位置即可。时间复杂度 \(\mathcal{O}(n^3\log n)\),空间复杂度滚动后可以做到 \(\mathcal{O}(n^3)\)。实测速度极快。
点击查看代码
#include<bits/stdc++.h>
#define y1 y3456
using namespace std;
const int inf=1e9;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int sum[255][255],f[2][255][255][255];char s[255][255];
int ask(int x1,int y1,int x2,int y2){
return sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
}
signed main(){
int n=read(),m=read();
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(s[i][j]=='#');
}
}
for(int x1=1;x1<=n;x1++){
for(int y1=1;y1<=m;y1++){
for(int x2=n,y2=y1;y2<=m;y2++){
while(x2>=x1){
if(ask(x1,y1,x2,y2)==0||ask(x1,y1,x2,y2)==(x2-x1+1)*(y2-y1+1))break;
x2--;
}
if(x2<x1)break;
f[0][x1][y1][y2]=x2;
}
}
}
if(f[0][1][1][m]==n)return puts("0"),0;
for(int k=1;;k++){
int o=k&1;
for(int x1=1;x1<=n;x1++){
for(int y1=1;y1<=m;y1++){
int pos=y1;
for(int y2=y1;y2<=m;y2++){
while(pos+1<y2&&min(f[o^1][x1][y1][pos],f[o^1][x1][pos+1][y2])<=min(f[o^1][x1][y1][pos+1],f[o^1][x1][pos+2][y2]))pos++;
f[o][x1][y1][y2]=max({f[o^1][x1][y1][y2],f[o^1][f[o^1][x1][y1][y2]+1][y1][y2],min(f[o^1][x1][y1][pos],f[o^1][x1][pos+1][y2])});
}
}
}
if(f[o][1][1][m]==n)return printf("%d\n",k),0;
}
puts("-1");
return 0;
}
D 【1122 B组】小黄鸭与数列
考虑一个朴素 dp:\(f_{i,j,k}\) 表示前 \(i\) 个数,最后一个数先被除了 \(j\) 次,再被乘了 \(k\) 次的最小代价。为什么这样定义?因为如果先乘后除,除法可以和乘法抵消。一个数能被除 \(\mathcal{O}(\log V)\) 次,能被乘 \(\mathcal{O}(n)\) 次。令 \(m=\max\limits_{i=1}^n\{a_i\}\),状态数是 \(\mathcal{O}(n^2\log m)\) 级别的。可以做到 \(\mathcal{O}(1)\) 转移,见后文。
考虑优化这个玩意。我们把整个序列分成两半:前面的都不大于原来 \(a\) 数组中的最大值 \(m\),后面的都大于 \(m\)。前面一半还是一样的 dp,但是此时状态数优化到 \(\mathcal{O}(n\log^2m)\);后面一半可以倒着 dp。定义 \(g_{i,j}\) 表示考虑后 \(i\) 个数,最后一个数被除了 \(j\) 次,且使它刚好被乘到大于 \(m\) 的最小代价。这样做有一个好处,就是我们不再需要考虑位数大小,每个数的位数都是相同的。转移就是你枚举上一个数被除的次数 \(k\),令 \(i\) 被乘到刚好大于 \(m\) 时的值为 \(b_{i,j}\),那么如果 \(b_{i,j}>b_{i+1,k}\),说明后面每个数都需要再乘一个 2,转移的代价还需额外加上 \(n-i\),反之则不用,复杂度 \(\mathcal{O}(n\log^2m)\)。
怎么 \(\mathcal{O}(1)\) 转移前半部分?我们可以把 \(k-j\) 当成第一关键字从小到大排,把 \(j\) 当成第二关键字从大到小排,你改变一下枚举顺序就从小到大排好了所有的值。转移时在 \(i-1\) 和 \(i\) 的所有取值上双指针即可做到均摊 \(\mathcal{O}(1)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,SIZ=1e5;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n,m,a[100005],b[100005][25],c[100005][25],lim[100005],Log[100005];
struct Node{
int j,k,val;
}pos[2][305];
int tot[2],now[2],f[2][25][25],g[100005][25],G[100005];
signed main(){
int T=read(),id=read();
Log[1]=0;for(int i=2;i<=SIZ;i++)Log[i]=Log[i>>1]+1;
while(T--){
n=read(),m=0;
for(int i=1;i<=n;i++)a[i]=read(),m=max(m,a[i]);
for(int i=1;i<=n;i++){
int val=a[i],cnt=0;
while(val)b[i][cnt]=val,val>>=1,cnt++;
lim[i]=cnt-1;
}
G[n]=inf;
for(int j=0;j<=lim[n];j++){
c[n][j]=b[n][j],g[n][j]=j;
while(c[n][j]<=m)c[n][j]<<=1,g[n][j]++;
G[n]=min(G[n],g[n][j]);
}
for(int i=n-1;i>=1;i--){
G[i]=inf;
for(int j=0;j<=lim[i];j++){
c[i][j]=b[i][j],g[i][j]=j;
while(c[i][j]<=m)c[i][j]<<=1,g[i][j]++;
int tmp=inf;
for(int k=0;k<=lim[i+1];k++){
if(c[i][j]>c[i+1][k])tmp=min(tmp,g[i+1][k]+n-i);
else tmp=min(tmp,g[i+1][k]);
}
g[i][j]+=tmp,G[i]=min(G[i],g[i][j]);
}
}
int ans=G[1];tot[1]=0;
for(int kj=-lim[1];kj<=Log[m]-Log[a[1]]+1;kj++){
for(int j=lim[1];j>=0;j--){
int k=kj+j;if(k<0||((a[1]>>j)<<k)>m)continue;
pos[1][++tot[1]]=(Node){j,k,(a[1]>>j)<<k};
f[1][j][k]=j+k;ans=min(ans,f[1][j][k]+G[2]);
}
}
for(int i=2;i<=n;i++){
int o=i&1ll,res=inf,tmp=inf;tot[o]=0;
for(int kj=-lim[i];kj<=Log[m]-Log[a[i]]+1;kj++){
for(int j=lim[i];j>=0;j--){
int k=kj+j;if(k<0||((a[i]>>j)<<k)>m)continue;
pos[o][++tot[o]]=(Node){j,k,(a[i]>>j)<<k};
}
}
now[0]=now[1]=1;
while(now[0]<=tot[0]&&now[1]<=tot[1]){
if(pos[o^1][now[o^1]].val<=pos[o][now[o]].val)res=min(res,f[o^1][pos[o^1][now[o^1]].j][pos[o^1][now[o^1]].k]),now[o^1]++;
else f[o][pos[o][now[o]].j][pos[o][now[o]].k]=res+pos[o][now[o]].j+pos[o][now[o]].k,tmp=min(tmp,f[o][pos[o][now[o]].j][pos[o][now[o]].k]),now[o]++;
}
while(now[o]<=tot[o])f[o][pos[o][now[o]].j][pos[o][now[o]].k]=res+pos[o][now[o]].j+pos[o][now[o]].k,tmp=min(tmp,f[o][pos[o][now[o]].j][pos[o][now[o]].k]),now[o]++;
ans=min(ans,tmp+G[i+1]);
}
printf("%lld\n",ans);
for(int i=0;i<2;i++){
for(int j=0;j<=tot[i]+2;j++){
pos[i][j].j=pos[i][j].k=pos[i][j].val=0;
}
}
for(int i=0;i<2;i++)tot[i]=now[i]=0;
for(int i=0;i<=n+1;i++)G[i]=lim[i]=0;
for(int i=0;i<2;i++)for(int j=0;j<=Log[m]+1;j++)for(int k=0;k<=Log[m]+1;k++)f[i][j][k]=0;
for(int i=0;i<=n+1;i++)for(int j=0;j<=Log[m]+1;j++)g[i][j]=c[i][j]=b[i][j]=0;
}
return 0;
}

浙公网安备 33010602011771号