2021牛客OI赛前集训营-提高组(第一场)-(重现赛)赛后总结
牛表
https://ac.nowcoder.com/acm/contest/20106/A
时间 | 名称 | 赛制 | 组别 | 得分 | 排名 |
---|---|---|---|---|---|
2022.11.21 | 2021牛客OI赛前集训营(第一场) | OI | 提高组 | 177.5/400 | 22 |
注:买的去年的题,本地OI赛制,排名按当年的计算。
这次真属于是挂大分了,T1挂了10分,T2想出正解然而0分(没删中间行),状态太差了
A.牛表
最开始看错题了导致没怎么打表,连边就到 ,痛失 分。
其他没什么,构个图跑个 spfa 就好了(比 dijkstra 快 倍)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pa;
const int MAXN=2005;
const int MOD=998244353;
vector<pa>G[MAXN];
int inv[MAXN],dis[MAXN],q[MAXN*100];
bool vis[MAXN];
int p,t;
int quick_pow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=(long long)ret*a%MOD;
a=(long long)a*a%MOD,b>>=1;
}
return ret;
}
void spfa(int s)
{
int head=1,tail=0;
memset(dis,0x3f,sizeof(dis));
dis[s]=0,q[++tail]=s;
while(head<=tail)
{
int x=q[head++];
vis[x]=0;
for(auto i:G[x])
{
int y=i.first,z=i.second;
if(dis[y]>dis[x]+z)
{
dis[y]=dis[x]+z;
if(!vis[y]) q[++tail]=y,vis[y]=1;
}
}
}
}
int main()
{
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
cin>>p>>t;
inv[1]=1;
for(int i=1;i<p;++i)
for(int j=max(1,i-17);j<=min(p-1,i+17);++j)
G[i].push_back({i*j%p,abs(i-j)});
int ans=0;
for(int i=1;i<p;++i)
{
int mul=quick_pow(t,(i-1)*(p-1));
spfa(i);
for(int j=1;j<p;++j)
{
if(i==j){mul=(long long)mul*t%MOD;continue;}
ans+=(long long)dis[j]*mul%MOD;
if(ans>=MOD) ans-=MOD;
mul=(long long)mul*t%MOD;
}
}
cout<<ans<<endl;
//printf("time:%lfs",(double)clock()/CLOCKS_PER_SEC);
return 0;
}
B.牛牛和数组操作
设 为操作区间 所需花费的最小牛币。
那么显然有转移方程:。
设 为操作区间 花费的最小牛币的方案数,集合 为满足操作区间 花费牛币最小的切分点。
所以有:。
解释一下这个转移方程是怎么来的, 表示选择子区间 和 的各一种合法排列,就等价于从 中选 个位置这些位置子区间 的操作,那么剩下的位置就是子区间 的操作,不难发现一但选定组合排列一定是定下来的。
由于子区间共 个,区间平均长度为 ,常数很小,足以通过本题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define inv(x) quick_pow(x,MOD-2)
const int MAXN=1005;
const int MOD=998244353;
int a[MAXN],dp[MAXN][MAXN],cnt[MAXN][MAXN],premax[MAXN],sufmax[MAXN],fac[MAXN],inv_fac[MAXN];
int quick_pow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=(long long)ret*a%MOD;
a=(long long)a*a%MOD,b>>=1;
}
return ret;
}
int C(int n,int m){return (long long)fac[n]*inv_fac[m]%MOD*inv_fac[n-m]%MOD;}
int main()
{
freopen("B.in","r",stdin);
freopen("B.out","w",stdout);
int n;
cin>>n;
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=(long long)fac[i-1]*i%MOD;
inv_fac[n]=inv(fac[n]);
for(int i=n-1;i>=0;--i) inv_fac[i]=(long long)inv_fac[i+1]*(i+1)%MOD;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=0;i<=n+1;++i) for(int j=0;j<=n+1;++j) cnt[i][j]=1;
for(int len=1;len<=n;++len)
{
for(int l=1;l+len-1<=n;++l)
{
int r=l+len-1;
premax[l-1]=0;
for(int i=l;i<=r;++i) premax[i]=max(premax[i-1],a[i]);
sufmax[r+1]=0;
for(int i=r;i>=l;--i) sufmax[i]=max(sufmax[i+1],a[i]);
dp[l][r]=0x3f3f3f3f;
for(int i=l;i<=r;++i)
{
if(dp[l][r]>dp[l][i-1]+dp[i+1][r]+premax[i-1]+sufmax[i+1]) dp[l][r]=dp[l][i-1]+dp[i+1][r]+premax[i-1]+sufmax[i+1],cnt[l][r]=(long long)cnt[l][i-1]*cnt[i+1][r]%MOD*C(r-l,i-l)%MOD;
else if(dp[l][r]==dp[l][i-1]+dp[i+1][r]+premax[i-1]+sufmax[i+1]) (cnt[l][r]+=(long long)cnt[l][i-1]*cnt[i+1][r]%MOD*C(r-l,i-l)%MOD)%=MOD;
}
}
}
cout<<cnt[1][n]<<endl;
// printf("time:%lfs",(double)clock()/CLOCKS_PER_SEC); //考场忘删这一行了,痛失100pts
return 0;
}
C.与巨
首先由 有 ,又因为 ,得 。
那么 等价于 ,即
设 ,那么 一定要是 的倍数。
枚举 ,设 :
- 则 的最高位都是 .
答案即为:
- ,要满足 。
但注意这样答案会多加上 ,减掉就行了。
注意这里要特判 种情况:
-
为偶数,答案为 。
-
,答案为 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define inv(x) quick_pow(x,MOD-2)
typedef long long ll;
const int MAXN=1e7+5;
const int MOD=998244353;
int two[MAXN];
char s[MAXN];
ll c;
int quick_pow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=(ll)ret*a%MOD;
a=(ll)a*a%MOD,b>>=1;
}
return ret;
}
int main()
{
freopen("C.in","r",stdin);
freopen("C.out","w",stdout);
two[0]=1;
for(int i=1;i<MAXN;++i) two[i]=two[i-1]*2%MOD;
int T;
cin>>T;
while(T--)
{
scanf("%s %lld",s+1,&c);
if(c%2==0){printf("0\n");continue;}
if(c==1)
{
int n=0,m=strlen(s+1);
for(int i=1;i<=m;++i)
{
n=(n<<1)+s[i]-'0';
if(n>=MOD) n-=MOD;
}
int ans=(long long)n*(n+1)/2%MOD;
printf("%d\n",ans);
continue;
}
int m=strlen(s+1),p=0,n=0;
for(int i=1;i<=m;++i)
{
n=(n<<1)+s[i]-'0';
if(n>=MOD) n-=MOD;
}
--c;
while(c && !(c&1)) ++p,c>>=1;
int ans=0;
for(int t=0;t<m-1;++t)
{
int g=max(0,t+1-p),x=two[t-g]-1;
(ans+=((ll)(x+1)*two[t]%MOD+((ll)(x+1)*x/2)%MOD*two[g]%MOD)*two[g]%MOD)%=MOD;
}
int t=m-1,g=max(0,t+1-p),x=0;
for(int i=1;i<=m-g;++i)
{
x=(x<<1)+s[i]-'0';
if(x>=MOD) x-=MOD;
}
x=(x-two[t-g]+MOD)%MOD;
(ans+=((ll)(x+1)*two[t]%MOD+((ll)(x+1)*x/2)%MOD*two[g]%MOD)*two[g]%MOD)%=MOD;
ans=((ll)ans-((ll)two[t]+(ll)(x+1)*two[g]%MOD-1-n+MOD)*((ll)two[t]+(ll)x*two[g]%MOD)%MOD+MOD)%MOD;
printf("%d\n",ans);
}
return 0;
}
D.矩阵学说
分做法:
枚举矩阵左上顶点 ,矩阵大小 ,用二维 表 查询,时间复杂度 。
分做法:
枚举矩阵左上顶点 ,二分满足 ,用二维 表 查询,时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1505;
const int MAXM=105;
int a[MAXN][MAXN],lg[MAXN];
bitset<MAXM>st[MAXN][MAXN][11];
int n,m,K;
void init()
{
for(int k=1;k<=10;++k)
for(int i=1;i+(1<<k)-1<=n;++i)
for(int j=1;j+(1<<k)-1<=m;++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];
}
int query(int x1,int y1,int x2,int y2)
{
int k=lg[x2-x1+1];
return (st[x1][y1][k]|st[x2-(1<<k)+1][y1][k]|st[x1][y2-(1<<k)+1][k]|st[x2-(1<<k)+1][y2-(1<<k)+1][k]).count();
}
long long solve(int k)
{
long long ans=0;
for(int x=1;x<=n;++x)
{
for(int y=1;y<=m;++y)
{
int l=1,r=min(n-x+1,m-y+1),ret=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(query(x,y,x+mid-1,y+mid-1)<=k) ret=mid,l=mid+1;
else r=mid-1;
}
ans+=ret;
}
}
return ans;
}
int main()
{
freopen("D.in","r",stdin);
freopen("D.out","w",stdout);
for(int i=2;i<MAXN;++i) lg[i]=lg[i>>1]+1;
cin>>n>>m>>K;
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;
init();
cout<<solve(K)-solve(K-1);
return 0;
}
//后记&总结:本次考试状态极差,前两题看错题了以为不可做,然后还剩 1h 30min 时才只有 20 分,懂题意后猛然醒悟,如果早点读懂题意应该会更从容一点,也不至于挂 110 分。
笔者能力有限,有未述详尽或有误之处,欢迎指正。