【题解】2021牛客OI赛前集训营-提高组(第一场)

A.

最优方案中 a n s [ i ] [ j ] ≤ 17 ans[i][j]\leq17 ans[i][j]17

所以只要把 ≤ 17 \leq17 17 的边连起来就好了

单次 dijkstra \text{dijkstra} dijkstra 时间复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

这道题如果把堆去掉换成队列的话时间复杂度是 O ( 17 n 2 ) O(17n^2) O(17n2)

#include<bits/stdc++.h> #define db double #define ll long long #define mkp make_pair #define pii pair<int,int> #define inf 0x3f3f3f3f #define fi first #define se second using namespace std; const int Maxn=2000*2000+5; const int mod=998244353; int P,t,ans[2005][2005],vis[2005][2005]; ll val[Maxn],res; priority_queue<pii> q; int has(int i,int j) { return (i-1)*(P-1)+j-1; } bool solve(int st) { for(int i=1;i<P;i++) ans[st][i]=inf,vis[st][i]=0; while(q.size()) q.pop(); int cnt=P-1; q.push(mkp(0,st)); ans[st][st]=0; while(q.size()) { int x=q.top().se,tmp=ans[st][x]; q.pop(); if(vis[st][x]) continue; vis[st][x]=1; cnt--; if(cnt==0) return 1; for(int j=max(1,x-20);j<=min(P-1,x+20);j++) { int nx=x*j%P; if(ans[st][x]+abs(x-j)<ans[st][nx]) { ans[st][nx]=ans[st][x]+abs(x-j); q.push(mkp(-ans[st][nx],nx)); } } } return 0; } int main() { memset(ans,0x3f,sizeof(ans)); scanf("%d%d",&P,&t); val[0]=1; for(int i=1;i<(P-1)*(P-1);i++) { val[i]=val[i-1]*t%mod; } for(int i=1;i<P;i++) { solve(i); for(int j=1;j<P;j++) { res=(res+ans[i][j]*val[has(i,j)]%mod)%mod; } } printf("%lld",res); }

B.

考虑区间 dp 。

dp 转移非常显然。

但是你会被卡常数。

有一个结论:区间的第一次操作一定是区间最大值。

考虑怎么证明它:如果先操作 max ,再操作 x ,代价为 L + R + L1 + R1 ;如果先操作 x ,那么代价为 Max + R1 + L + L1 ,显然先操作最大值比较优。

根据 不等式传递性 ,我们每次交换操作序列中 Max 到左边会使答案更优,结论得证。(我们得到了本质上的最优策略 233)

加上这个剪枝,时间复杂度 O ( n 3 ) O(n^3) O(n3) 。(虽然复杂度没变 233)

#include<bits/stdc++.h> #define db double #define ll long long #define mkp make_pair #define pii pair<int,int> #define inf 0x3f3f3f3f #define fi first #define se second using namespace std; const int Maxn=2005; const int mod=998244353; //结论:最优决策一定是区间 [l,r] 的最大值 int n,Log[Maxn],a[Maxn],st[Maxn][20],dp[Maxn][Maxn]; ll dp2[Maxn][Maxn],c[Maxn][Maxn]; int ask(int l,int r) { if(l>r) return 0; int k=Log[r-l+1]; return max(st[l][k],st[r-(1<<k)+1][k]); } int main() { scanf("%d",&n); for(int i=2;i<=n;i++) Log[i]=Log[i/2]+1; for(int i=1;i<=n;i++) scanf("%d",&a[i]),st[i][0]=a[i]; for(int j=1;j<20;j++) { for(int i=1;i<=n-(1<<j)+1;i++) { st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]); } } for(int i=0;i<=n;i++) c[i][0]=1; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod; } } for(int i=1;i<=n;i++) dp[i][i]=0,dp2[i][i]=1; for(int i=1;i<=n+1;i++) dp2[i][i-1]=1; for(int len=2;len<=n;len++) { for(int i=1;i<=n-len+1;i++) { int j=i+len-1; dp[i][j]=inf; for(int k=i;k<=j;k++) { if(a[k]!=ask(i,j)) continue; if(dp[i][j]==ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j]) { dp2[i][j]=(dp2[i][j]+dp2[i][k-1]*dp2[k+1][j]%mod*c[j-i][k-i]%mod)%mod; } else if(dp[i][j]>ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j]) { dp[i][j]=ask(i,k-1)+ask(k+1,j)+dp[i][k-1]+dp[k+1][j]; dp2[i][j]=dp2[i][k-1]*dp2[k+1][j]%mod*c[j-i][k-i]%mod; } } } } printf("%lld",dp2[1][n]); }

C.

非常恶心的模拟题。

很容易看出来答案是若干个等差数列求和。

暴力枚举 i ∈ [ 2 t , 2 t + 1 ) i\in [2^t,2^{t+1}) i[2t,2t+1) ,首先不难推出 i ∗ ( c − 1 ) m o d    2 t + 1 = 0 i*(c-1)\mod 2^{t+1} = 0 i(c1)mod2t+1=0

c − 1 c-1 c1 进行 2 2 2 的因数分解,即: i m o d    2 g = 0 i\mod 2^{g}=0 imod2g=0 ,其中 g = max ⁡ ( 0 , t + 1 − p ) g=\max(0,t+1-p) g=max(0,t+1p)

这里不难看出两件事:

  1. 2 g 2^g 2g 个数的函数值相同,一共有 2 t 2^t 2t 个数,所以有 2 g − t 2^{g-t} 2gt 组,而每一组之间的公差为 2 g 2^g 2g
  2. 如果 i < n − 1 i<n-1 i<n1 的话这个组可以恰好分完

我们回顾一下等差数列求和公式:首项 * 项数 + 项数 * (项数-1) /2 * 公差

带入上式可以得到答案。

现在我们来解决 i = n − 1 i=n-1 i=n1 的情况。

我们分为散块和整块。

散块比较简单,只要能整除 2 g 2^g 2g 部分的都是整块(换句话说 1 + 前 2~n-g 位)

后面 n-g+1~n 位就都是散块了。由于我们把最后这部分散块看成了整块,所以多算了 2 g 2^g 2g - sk - 1 个数 ,末项又可以用 首项 + (项数-1) * 公差 来求得,所以我们就做完了。时间复杂度是 O(n) 的。

总结:这道题纯用了数学方法来推导,每一个部分的求和都特别清晰,必须对题目性质有较深刻的认识才能做得出来。

#include<bits/stdc++.h> #define db double #define ll long long #define mkp make_pair #define pii pair<int,int> #define inf 0x3f3f3f3f #define fi first #define se second using namespace std; const int mod=998244353; const int Maxn=1e7+5; char s[Maxn]; ll n,m,p,c,res,ksm[Maxn]; //考虑数列 dp(i) 到底长什么样子 //i \in [2^p,2^{p+1}) //i * (c-1) mod 2^p = 0 //显然当 i = 2^p 是恒成立的,此时 dp(i) = 2^p //如果 c-1 是 2^p 的倍数 ,这一段就是等差数列求和 // ll calc(ll sx,ll gc,ll xs) { return (sx*xs%mod+xs*(xs-1)/2%mod*gc%mod)%mod; } int main() { // freopen("data.in","r",stdin); ksm[0]=1; for(int i=1;i<=1e7;i++) ksm[i]=ksm[i-1]*2%mod; int T; scanf("%d",&T); while(T--) { scanf("%s%lld",s+1,&c),n=strlen(s+1); // reverse(s+1,s+1+n); int tmp=0; for(int i=1;i<=n;i++) { tmp=tmp*2+s[i]-'0'; } // printf("%d\n",tmp); c--; if(c&1) { printf("0\n"); continue; } if(c==0) { ll tmp=0; for(int i=1;i<=n;i++) { tmp=(tmp*2+s[i]-'0')%mod; } printf("%lld\n",tmp*(tmp+1)/2%mod); continue; } p=0; res=0; while(c%2==0) p++,c/=2; for(int i=0;i<n;i++) { //等差数列三要素:首项,公差,项数 //首项 * 项数 + 项数 * (项数-1) /2 * 公差 //首项 = 2^i if(i<n-1) { ll sx=ksm[i],gc=max(i+1-p,0ll),xs=ksm[i-gc]; //公差 = 2^{max(i+1-p,0)} //项数 = 2^{i-gc} res=(res+ksm[gc]*calc(sx,ksm[gc],xs)%mod)%mod; } else { //项数可以取模哟 ll sx=ksm[i],gc=max(i+1-p,0ll),xs=0,sk=0,mx=0; for(int j=1;j<=n-gc;j++) { xs=(xs*2+s[j]-'0')%mod; } xs=(xs-ksm[i-gc]+1+mod)%mod; //计算整块 // printf("%d %d %d %d\n",sx,ksm[gc],xs,gc); res=(res+ksm[gc]*calc(sx,ksm[gc],xs)%mod)%mod; //最后一项被多记数了 2^g - sk - 1次 for(int j=n-gc+1;j<=n;j++) { sk=(sk*2+s[j]-'0')%mod; } mx=(sx+(xs-1)*ksm[gc]%mod)%mod; res=(res-(ksm[gc]-sk-1+mod)%mod*mx%mod+mod)%mod; //计算散块 // res=(res+sk*mx%mod)%mod; } // printf("%d\n",res); } printf("%lld\n",res); } }

D.

二维 st 表 + bitset + 二分。

做法很显然就不说了。主要是观察到 a [ i ] [ j ] ≤ 100 a[i][j]\leq100 a[i][j]100 所以直接上 bitset \text{bitset} bitset (真可谓暴力神器)

时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 。(可能常数略大 233)

#include<bits/stdc++.h> #define db double #define ll long long #define mkp make_pair #define pii pair<int,int> #define inf 0x3f3f3f3f #define fi first #define se second using namespace std; const int Maxn=1505; //二维 st 表 + bitset int n,m,Log[Maxn],a[Maxn][Maxn],k,res; bitset<101> st[Maxn][Maxn][11]; inline int read() { int x=0,f=1; char c=getchar(); while(c<'0'||c>'9') { c=getchar(); } while(c>='0'&&c<='9') { x=(x<<1)+(x<<3)+c-'0'; c=getchar(); } return x; } int qry(int x,int y,int d) { int k=Log[d]; return (st[x][y][k]|st[x+d-(1<<k)][y][k]|st[x][y+d-(1<<k)][k]|st[x+d-(1<<k)][y+d-(1<<k)][k]).count(); } int solve(int x,int y,int k) { if(k==0) return 0; int l=1,r=min(n-x+1,m-y+1),res=0; while(l<=r) { int mid=l+r>>1; if(qry(x,y,mid)<=k) res=mid,l=mid+1; else r=mid-1; } return res; } int main() { n=read(),m=read(),k=read(); for(int i=2;i<=max(n,m);i++) { Log[i]=Log[i/2]+1; } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { scanf("%d",&a[i][j]); st[i][j][0][a[i][j]]=1; } } for(int k=1;k<=10;k++) { for(int i=1;i<=n-(1<<k)+1;i++) { for(int j=1;j<=m-(1<<k)+1;j++) { st[i][j][k]=st[i][j][k-1]|st[i+(1<<k-1)][j][k-1]|st[i][j+(1<<k-1)][k-1]|st[i+(1<<k-1)][j+(1<<k-1)][k-1]; } } } for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { int tmp=solve(i,j,k); if(!tmp||qry(i,j,tmp)!=k) continue; res+=tmp-solve(i,j,k-1); } } printf("%d",res); }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530228.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示