2024 Noip 做题记录(四)
Round #13 - 2024.10.1
A. [ARC165D] Compare
题目大意
判断是否存在一个长度为
的字符串 ,满足 个限制 ,要求 字典序小于 。 数据范围:
。
思路分析
从一些必要条件开始,对每个限制显然有
在这张拓扑图上,如果有环,那么说明环上元素必须相等,因此可以把每个强联通分量都缩点。
注意到缩点后相当于确定若干
如果某个时刻
每次 Tarjan 缩点至少合并两个位置,因此合并至多进行
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2005;
int n,m,a[MAXN],b[MAXN],c[MAXN],d[MAXN],dsu[MAXN];
int find(int x) { return dsu[x]^x?dsu[x]=find(dsu[x]):x; }
vector <int> G[MAXN];
int dfn[MAXN],low[MAXN],dcnt,stk[MAXN],tp;
bool ins[MAXN],ok;
void tarjan(int u) {
dfn[u]=low[u]=++dcnt,ins[stk[++tp]=u]=true;
for(int v:G[u]) {
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]) {
ok&=stk[tp]==u;
while(ins[u]) dsu[find(u)]=find(stk[tp]),ins[stk[tp--]]=false;
}
}
signed main() {
scanf("%d%d",&n,&m),iota(dsu+1,dsu+n+1,1);
for(int i=1;i<=m;++i) scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
while(true) {
for(int i=1;i<=n;++i) G[i].clear(),dfn[i]=low[i]=0;
for(int i=1;i<=m;++i) {
while(a[i]<=b[i]&&c[i]<=d[i]&&find(a[i])==find(c[i])) ++a[i],++c[i];
if(c[i]>d[i]) return puts("No"),0;
if(a[i]<=b[i]) G[find(a[i])].push_back(find(c[i]));
}
ok=true,dcnt=0;
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
if(ok) break;
}
puts("Yes");
return 0;
}
B. [ARC169D] Modify
题目大意
给定
,每次操作可以给 个不同的 加一(在 意义下),求至少几次操作能让 变成 的排列。 数据范围:
。
思路分析
考虑在不
。 互不相同。- 记
,那么 。 。
首先只有第一个条件和
然后考虑第四个条件,对于一组合法解
显然不影响条件一、二、三的合法性,且此时
因此这样调整一定更优,最终一定有
那么逐个满足条件即可,先找到
找到此时的
此时
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5;
int n,m,a[MAXN],g;
ll sum,k=0;
signed main() {
scanf("%d%d",&n,&m),g=__gcd(n,m);
for(int i=0;i<n;++i) scanf("%d",&a[i]),sum+=a[i];
if((1ll*n*(n-1)/2-sum)%g) return puts("-1"),0;
sort(a,a+n);
for(int i=0;i<n;++i) k=max(k,(ll)a[i]-i);
sum=(2*k+n-1)*n/2-sum;
while(sum%m) ++k,sum+=n;
ll mx=0,d=m/__gcd(n,m),c=n*d/m;
for(int i=0;i<n;++i) mx=max(mx,k+i-a[i]);
if(mx>sum/m) {
ll cur=(mx-sum/m-1)/(c-d)+1;
printf("%lld\n",sum/m+cur*c);
} else printf("%lld\n",sum/m);
return 0;
}
C. [ARC168E] Devide
题目大意
给定
,将其分成 个非空区间,最大化元素和 的区间个数。 数据范围:
。
思路分析
作为一道数列分段题,先观察代价函数,发现
但我们可以进行一些观察,对于元素和
因此可以二分,假设有
我们只要计算分
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5;
int n,k,pre[MAXN];
ll S,sum[MAXN];
array <ll,2> dp[MAXN];
array<ll,2> wqs(ll p) {
dp[0]={0,0};
for(int i=1;i<=n;++i) {
dp[i]=dp[i-1];
if(~pre[i]) dp[i]=min(dp[i],{dp[pre[i]][0]+i-pre[i]-p,dp[pre[i]][1]+1});
}
return dp[n];
}
bool chk(int x) {
int l=1,r=n,z=0;
while(l<=r) {
int mid=(l+r)>>1;
if(wqs(mid)[1]<=x) z=mid,l=mid+1;
else r=mid-1;
}
return wqs(z)[0]+1ll*x*z<=n-(k-x);
}
signed main() {
scanf("%d%d%lld",&n,&k,&S);
for(int i=1;i<=n;++i) scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
for(int i=1,j=-1;i<=n;++i) {
while(sum[i]-sum[j+1]>=S) ++j;
pre[i]=j;
}
int l=1,r=k,z=0;
while(l<=r) {
int mid=(l+r)>>1;
if(chk(mid)) z=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",z);
return 0;
}
D. [ARC170E] Expand
题目大意
在一个长度为
的环进行 bfs,初始在点 ,每次取出当前点相邻的所有未访问过的点,按编号大小顺序更新最短路,并依次加入队列,其中每个点有 的概率加入队首,有 的概率加入队尾。求所有点的距离之和的期望。 数据范围:
。
思路分析
注意到任何时候队里只有两个元素,每次会更新队首元素的后继,然后以
那么可以 dp,设
不难用一个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef array<array<ll,4>,4> Mat;
const int MOD=998244353,inv=828542813;
inline Mat operator *(const Mat &u,const Mat &v) {
Mat w; w.fill({0,0,0,0});
for(int i:{0,1,2,3}) for(int k:{0,1,2,3}) if(u[i][k]) for(int j:{0,1,2,3}) {
w[i][j]=(w[i][j]+u[i][k]*v[k][j])%MOD;
}
return w;
}
void solve() {
ll n,p;
scanf("%lld%lld",&n,&p),--n,p=inv*p%MOD;
Mat I,X; //f g 1 ans
I.fill({0,0,0,0}),X.fill({0,0,0,0});
I[0][0]=p,I[0][1]=1+MOD-p,I[0][3]=1;
I[1][0]=1+MOD-p,I[1][1]=p;
I[2][0]=p,I[2][1]=1+MOD-p,I[2][2]=1,I[2][3]=1;
I[3][3]=1,X[0][2]=1;
for(;n;n>>=1,I=I*I) if(n&1) X=X*I;
printf("%lld\n",X[0][3]);
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*E. [ARC169F] Table
题目大意
给定
排列 ,定义 ,其中 ,求 。 数据范围:
。
思路分析
题目相当于给出一个
考虑
不难发现如果
分析发现如果某一行
因此
我们要对这样的区间计数,设
然后就是要求
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2.5e5+5,MOD=998244353;
int n,stk[MAXN],tp;
void solve(int *a,int *l,int *r,ll *f) {
stk[tp=0]=0;
for(int i=1;i<=n;++i) {
while(tp&&a[stk[tp]]>a[i]) r[stk[tp--]]=i;
l[i]=stk[tp],stk[++tp]=i;
}
while(tp) r[stk[tp--]]=n+1;
for(int i=1;i<=n;++i) f[a[i]]=1ll*(r[i]-i)*(i-l[i])%MOD;
for(int i=2*n;i;--i) f[i]=(f[i]+f[i+1])%MOD;
}
int a[MAXN],b[MAXN],la[MAXN],ra[MAXN],lb[MAXN],rb[MAXN],wa[MAXN],wb[MAXN];
ll fa[MAXN<<1],fb[MAXN<<1];
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) scanf("%d",&b[i]);
for(int i=1;i<=n;++i) scanf("%d",&wa[i]);
for(int i=1;i<=n;++i) scanf("%d",&wb[i]);
solve(a,la,ra,fa),solve(b,lb,rb,fb);
ll ans=0;
stk[tp=0]=n+1;
for(int i=n;i>=1;--i) {
for(;tp&&a[stk[tp]]>a[i];--tp) {
ans+=wa[i]*(fb[a[i]]-fb[a[stk[tp]]])%MOD*(stk[tp-1]-stk[tp])%MOD;
}
ans+=wa[i]*fb[a[i]]%MOD,stk[++tp]=i;
}
stk[tp=0]=n+1;
for(int i=n;i>=1;--i) {
for(;tp&&b[stk[tp]]>b[i];--tp) {
ans+=wb[i]*(fa[b[i]]-fa[b[stk[tp]]])%MOD*(stk[tp-1]-stk[tp])%MOD;
}
ans+=wb[i]*fa[b[i]]%MOD,stk[++tp]=i;
}
ans-=1ll*n*n%MOD*wa[1]%MOD;
printf("%lld\n",(ans%MOD+MOD)%MOD);
return 0;
}
Round #14 - 2024.10.2
A. [ARC172D] Distance
题目大意
给定
个 维空间中的点,已知他们两两之间距离的大小关系(不存在相等的距离),在 范围内构造一组可能的方案。 数据范围:
。
思路分析
先考虑如何构造若干个距离两两相等的点,可以设
然后考虑调整,如果给某个
因此我们发现当
我们可以取
容易发现
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int K=1e8;
int n,a[25][25];
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) a[i][i]=K;
for(int i=n*(n-1)/2,x,y;i;--i) scanf("%d%d",&x,&y),a[x][y]=i;
for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=n;++j) printf("%d ",a[i][j]);
return 0;
}
*B. [ARC139E] Grid
题目大意
给定
循环网格(第 行、第 列相邻),求有多少种选出格子数最多的方案,使得选出的格子互不相邻。 数据范围:
。
思路分析
如果
否则不妨设
那么设
这是显然的,因为每一行
然后考虑如何数方案数,注意到每一行的未选格子都是间隔排列,但是有两个位置是相邻的,即未被选的格子排列形如:
不妨设第
因此我们只要数有多少长度为
由于我们钦定
- 如果
,此时 ,所求相当于 ,直接多项式快速幂计算。 - 如果
,枚举序列中 个数,组合数计算即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+5,MOD=998244353;
namespace P {
const int N=1<<18,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
}
using namespace P;
int f[N],g[N],h[N];
signed main() {
ll n,m; scanf("%lld%lld",&n,&m),poly_init();
if(n%2==0&&m%2==0) return puts("2"),0;
if(m%2==0||(n%2==1&&m%2==1&&n<m)) swap(n,m);
if(n<MAXN) {
int ans=0;
for(int i=0;i<=n;++i) if(i%m==(n-i)%m) ans=(ans+1ll*fac[n]*ifac[i]%MOD*ifac[n-i])%MOD;
return printf("%lld\n",m%MOD*ans%MOD),0;
}
g[1]=g[m-1]=f[0]=1;
for(;n;n>>=1) {
ntt(g,0,N);
if(n&1) {
ntt(f,0,N);
for(int i=0;i<N;++i) h[i]=1ll*f[i]*g[i]%MOD;
ntt(h,1,N);
memset(f,0,sizeof(f));
for(int i=0;i<N;++i) f[i%m]=(f[i%m]+h[i])%MOD;
}
memset(h,0,sizeof(h));
for(int i=0;i<N;++i) h[i]=1ll*g[i]*g[i]%MOD;
ntt(h,1,N);
memset(g,0,sizeof(g));
for(int i=0;i<N;++i) g[i%m]=(g[i%m]+h[i])%MOD;
}
printf("%lld\n",m%MOD*f[0]%MOD);
return 0;
}
C. [ABC370G] Product
题目大意
求有多少长度为
的序列元素乘积 不超过 ,且 的因数个数和是 的倍数。 数据范围:
。
思路分析
假设
设答案为
我们要求的就是积性函数
预处理出
然后要考虑
这并不好维护,但我们可以反面考虑计算因数个数和不是
还是要处理
直接设
考虑作差,即计算
那么
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,MOD=998244353,r[3]={0,1,-1},i2=(MOD+1)/2;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
bool isc[MAXN];
int p[MAXN],tot,idx1[MAXN],idx2[MAXN],m;
ll n,q,B,v[MAXN],fp2[MAXN],g1[MAXN],g2[MAXN],h[45];
int id(ll x) { return x<=B?idx1[x]:idx2[n/x]; }
ll S1(ll N,int k) {
if(p[k]>N) return 0;
ll s=(g1[id(N)]+MOD-k)%MOD*q%MOD;
for(int i=k+1;i<=tot&&1ll*p[i]*p[i]<=N;++i) for(ll c=1,w=p[i];w<=N;++c,w*=p[i]) {
s=(s+h[c]*((c>1)+S1(N/w,i)))%MOD;
}
return s;
}
ll S2(ll N,int k) {
if(p[k]>N) return 0;
ll s=(g2[id(N)]+MOD-fp2[k])%MOD*q%MOD;
for(int i=k+1;i<=tot&&1ll*p[i]*p[i]<=N;++i) for(ll c=1,w=p[i];w<=N;++c,w*=p[i]) {
if((w*p[i]-1)/(p[i]-1)%3) s=(s+h[c]*((c>1)+S2(N/w,i)))%MOD;
}
return s;
}
signed main() {
scanf("%lld%lld",&n,&q),B=sqrt(n);
for(int i=h[0]=1;i<=40;++i) h[i]=h[i-1]*(q+i-1)%MOD*ksm(i)%MOD;
for(int i=2;i<=B;++i) {
if(!isc[i]) p[++tot]=i;
for(int j=1;j<=tot&&i*p[j]<=B;++j) {
isc[i*p[j]]=true;
if(i%p[j]==0) break;
}
}
for(int i=1;i<=tot;++i) fp2[i]=fp2[i-1]+r[p[i]%3];
for(ll l=1;l<=n;l=n/v[m]+1) {
v[++m]=n/l;
if(v[m]<=B) idx1[v[m]]=m;
else idx2[n/v[m]]=m;
}
for(int i=1;i<=m;++i) {
g1[i]=(v[i]-1)%MOD;
g2[i]=((v[i]-1)/3+MOD-(v[i]+1)/3)%MOD;
}
for(int k=1;k<=tot;++k) for(int i=1;i<=m&&1ll*p[k]*p[k]<=v[i];++i) {
g1[i]=(g1[i]+(k-1)+MOD-g1[id(v[i]/p[k])])%MOD;
g2[i]=(g2[i]+MOD+r[p[k]%3]*(fp2[k-1]+MOD-g2[id(v[i]/p[k])]))%MOD;
}
for(int i=1;i<=m;++i) g2[i]=(g1[i]+g2[i]+(v[i]>=3))*i2%MOD;
for(int i=1;i<=tot;++i) fp2[i]=fp2[i-1]+(p[i]%3!=2);
printf("%lld\n",(S1(n,0)+MOD-S2(n,0))%MOD);
return 0;
}
*D. [ARC138E] Decrease
题目大意
给定
,定义一个序列 是好的当且仅当:
。 - 如果
,那么 。 求所有好的序列
的长度为 且不包含 的下降子序列个数之和。 数据范围:
。
思路分析
考虑对于
那么连边之后会把
很显然这些边不可能来自于同一条链,并且这些边存在两两包含关系,此时最内层的边就会把这
枚举这
其中
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005,MOD=1e9+7;
ll S[MAXN][MAXN],w[MAXN],C[MAXN][MAXN];
signed main() {
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<=n+1;++i) {
w[i]=S[i][0]=!i,C[i][0]=1;
for(int j=1;j<=i;++j) {
C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
S[i][j]=(S[i-1][j-1]+S[i-1][j]*j)%MOD;
w[i]=(w[i]+S[i][j])%MOD;
}
}
ll ans=0;
for(int i=k;i<=n+1;++i) for(int j=k;i+j<=n+1;++j) {
ans=(ans+C[n+1][i+j]*S[i][k]%MOD*S[j][k]%MOD*w[n+1-i-j])%MOD;
}
printf("%lld\n",ans);
return 0;
}
*E. [ARC138F] Split
题目大意
给定
个点 , 是 排列,你要对这个点集进行若干次操作,得到一个序列。 如果
,序列中只含有这个点,否则选择如下两种操作之一:
- 选择一个
,把 的点操作得到的序列和 的点操作得到的序列拼起来。 - 选择一个
,把 的点操作得到的序列和 的点操作得到的序列拼起来。 求有能得到多少种不同的序列。
数据范围:
。
思路分析
计数操作序列是容易的,直接
但我们无法把操作序列和结果序列建立对应,考虑对一个若干等价的操作序列定义代表元。
对于一组结果序列,我们定义其代表元为每次操作贪心地取字典序最小的一个时的操作序列,其中所有操作的字典序按如下顺序排列:
考虑
那么我们 dp 时枚举最小操作
不妨容斥,设
同理如果
如果想让得到的结果序列相同,则
注意我们枚举的操作一定是非平凡操作,即
不断递归子问题即可,可以用记忆化搜索实现,用状压记录状态比较好写。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MOD=1e9+7;
int n,a[35];
unordered_map <int,ll> Q;
ll dp(int s) {
if(Q.count(s)) return Q[s];
if(__builtin_popcount(s)<=1) return 1;
vector <int> xi,yi;
for(int i=0;i<n;++i) if(s>>i&1) xi.push_back(i),yi.push_back(i);
sort(yi.begin(),yi.end(),[&](int i,int j){ return a[i]<a[j]; });
int k=xi.size();
vector <int> st;
for(int i=0,t=0;i<k-1;++i) {
st.push_back(t|=1<<xi[i]);
}
for(int i=0,t=0;i<k-1;++i) {
st.push_back(t|=1<<yi[i]);
}
vector <ll> f(2*k-2);
ll ans=0;
for(int i=0;i<2*k-2;++i) {
f[i]=dp(st[i]);
for(int j=0;j<i;++j) if((st[j]&st[i])==st[j]) f[i]=(f[i]-f[j]*dp(st[i]^st[j]))%MOD;
if(f[i]<0) f[i]+=MOD;
ans=(ans+f[i]*dp(s^st[i]))%MOD;
}
return Q[s]=ans;
}
signed main() {
scanf("%d",&n);
for(int i=0;i<n;++i) scanf("%d",&a[i]);
printf("%lld\n",dp((1<<n)-1));
return 0;
}
Round #15 - 2024.10.3
A. [ARC136E] Coprime
题目大意
构造一个 DAG,点集
, 当且仅当 且 ,求最大带权反链。 数据范围:
。
思路分析
注意到任意两个偶数之间都有连边,因此一个奇数
形式化来说,
均为偶数,一定可以。 为奇数, 为偶数, 。 为偶数, 为奇数, . 为奇数, 为奇数, 。
其中
可以把限制统一看成
那么一组反链就是若干个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e6+5;
int n,m,a[MAXN],p[MAXN],d[MAXN];
bool isc[MAXN];
ll f[MAXN],ans=0;
signed main() {
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=2;i<=n;++i) {
if(!isc[i]) p[++m]=i,d[i]=i;
for(int j=1;j<=m&&i*p[j]<=n;++j) {
isc[i*p[j]]=true,d[i*p[j]]=p[j];
if(i%p[j]==0) break;
}
}
f[1]+=a[1];
for(int i=2;i<=n;++i) {
if(i&1) f[i-d[i]+1]+=a[i],f[i+d[i]]-=a[i];
else f[i]+=a[i],f[i+1]-=a[i];
}
for(int i=1;i<=2*n;++i) f[i]+=f[i-1],ans=max(ans,f[i]);
printf("%lld",ans);
return 0;
}
B. [ARC137E] Profit
题目大意
给定
个点, 个操作,每个操作可以花费 的代价令 区间内的 加一,收益为 ,求最大收益减代价。 数据范围:
。
思路分析
考虑费用流。
类似志愿者招募那题的思路,连一条
这题我们考虑先给每个点选满,即答案记为
然后对于
对于一条
答案就是
本题需要用用原始对偶算法求费用流。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#include<atcoder/mincostflow>
#define ll long long
using namespace std;
signed main() {
int n,m,d;
scanf("%d%d%d",&n,&m,&d);
atcoder::mcf_graph <int,ll> G(n+1);
ll ans=0;
for(int i=1,A;i<=n;++i) {
scanf("%d",&A),ans+=1ll*A*d;
G.add_edge(i-1,i,A,d);
G.add_edge(i-1,i,m-A,0);
}
for(int i=1,l,r,c;i<=m;++i) {
scanf("%d%d%d",&l,&r,&c);
G.add_edge(l-1,r,1,c);
}
printf("%lld\n",ans-G.flow(0,n,m).second);
return 0;
}
*C. [ABC367G] Subset
题目大意
定义一个集合
的权值为其内部元素的异或和的 次方。 给定
,求其所有大小为 的倍数的子集的权值和。 数据范围:
。
思路分析
考虑用生成函数的语言描述,很显然我们同时关心集合大小和元素异或和,因此可以用
不妨把
考虑 FWT,那么
那么
我们只要求出这些
然后我们要求
不难发现这就等于
那么只要对每个
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+5,N=1<<20,MOD=998244353,i2=(MOD+1)/2;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,m,pw,c[N],f[MAXN][105],g[MAXN][105];
ll ans=0,w[MAXN],d[N]; //[z^0](1+z)^i(1-z)^n-i
inline void add(int &x,const int &y) { x=(x+y>=MOD)?x+y-MOD:x+y; }
inline void sub(int &x,const int &y) { x=(x>=y)?x-y:x+MOD-y; }
signed main() {
scanf("%d%d%d",&n,&m,&pw);
for(int i=1,x;i<=n;++i) scanf("%d",&x),++c[x];
for(int k=1;k<N;k<<=1) for(int i=0;i<N;i+=k<<1) for(int j=i;j<i+k;++j) {
int x=c[j],y=c[j+k]; c[j]=x+y,c[j+k]=x-y;
}
f[0][0]=g[0][0]=1;
for(int i=1;i<=n;++i) {
memcpy(f[i],f[i-1],sizeof(f[i]));
memcpy(g[i],g[i-1],sizeof(g[i]));
for(int j=0;j<m;++j) {
add(f[i][(j+1)%m],f[i-1][j]);
sub(g[i][(j+1)%m],g[i-1][j]);
}
}
for(int i=0;i<=n;++i) {
for(int j=0;j<m;++j) w[i]=(w[i]+1ll*f[i][j]*g[n-i][(m-j)%m])%MOD;
}
for(int s=0;s<N;++s) d[s]=w[(n+c[s])>>1];
for(int k=1;k<N;k<<=1) for(int i=0;i<N;i+=k<<1) for(int j=i;j<i+k;++j) {
ll x=d[j],y=d[j+k]; d[j]=(x+y)*i2%MOD,d[j+k]=(x-y)*i2%MOD;
}
for(int s=0;s<N;++s) ans=(ans+d[s]*ksm(s,pw))%MOD;
printf("%lld\n",(ans+MOD)%MOD);
return 0;
}
*D. [ARC136F] Flip
题目大意
给定
01 网格,每次操作随机翻转一个位置,求期望多少次操作后首次满足网格第 行恰有 个 。 数据范围:
。
思路分析
记
对于这种首次出现的问题,可以用 PGF 刻画:设
那么答案就是
因此我们只要求
注意到
那么我们注意到
那么 dp 预处理出
记
不难用单位根反演计算出
二项式定理展开并化简得到:
把后面的式子用 OGF 表示得到:
预处理出所有的
我们只要求
因此可以考虑求
记
时: , 时 ,此时对 无贡献,对 的贡献是 ,即 。 时: , ,对 贡献为 ,对 贡献为 。
类似求出
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2505,MOD=998244353,i2=(MOD+1)/2;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n,H,W;
int a[MAXN],b[MAXN];
ll C[MAXN][MAXN],cf[MAXN],cg[MAXN],wf[MAXN],wg[MAXN],p[MAXN],q[MAXN];
//ans=f/g
void solc(int *s,int *t,ll *c) {
c[0]=1;
for(int i=1;i<=H;++i) {
memset(p,0,sizeof(p));
memset(q,0,sizeof(q));
for(int j=0;j<=s[i];++j) {
int k=t[i]-s[i]+j;
if(0<=k&&k<=W-s[i]) q[j+k]=(q[j+k]+C[s[i]][j]*C[W-s[i]][k])%MOD;
}
for(int j=0;j<=W;++j) for(int k=0;j+k<=n;++k) {
p[j+k]=(p[j+k]+q[j]*c[k])%MOD;
}
memcpy(c,p,sizeof(p));
}
}
void solw(ll *c,ll *w) {
memset(p,0x3f,sizeof(p));
for(int i=0;i<=n;++i) p[i]=C[n][i];
ll inv=ksm(i2,n);
for(int i=0;i<=n;++i) {
if(i) {
for(int j=1;j<=n;++j) p[j]=(p[j]+MOD-p[j-1])%MOD;
for(int j=n;j;--j) p[j]=(p[j-1]+MOD-p[j])%MOD;
p[0]=MOD-p[0];
}
for(int s=0;s<=n;++s) w[s]=(w[s]+c[i]*inv%MOD*p[s])%MOD;
}
}
signed main() {
ios::sync_with_stdio(false);
cin>>H>>W,n=H*W;
for(int i=0;i<=n;++i) for(int j=C[i][0]=1;j<=i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
for(int i=1;i<=H;++i) {
string s; cin>>s,a[i]=count(s.begin(),s.end(),'1');
}
for(int i=1;i<=H;++i) cin>>b[i];
solc(a,b,cf),solc(b,b,cg);
solw(cf,wf),solw(cg,wg);
ll f1=wf[n],g1=wg[n],fd=0,gd=0;
for(int i=0;i<n;++i) {
ll k=1ll*n*i2%MOD*ksm(i+MOD-n)%MOD;
fd=(fd+wf[i]*k)%MOD;
gd=(gd+wg[i]*k)%MOD;
}
printf("%lld\n",((g1*fd-f1*gd)%MOD+MOD)*ksm(g1*g1%MOD)%MOD);
return 0;
}
*E. [ARC137F] Cover
题目大意
次操作,每次在 范围内随机选两个点 ,然后覆盖 ,求最终每个位置被覆盖次数 的概率。 数据范围:
。
思路分析
由于我们是在实数范围内随机,因此选出重复元素的概率为
如果把每次操作覆盖的线段看成一对括号的话,那么一组操作方案就对应一个长度为
求出括号序列的
设
那么要求就是
由于
不难证明每组
并且第
那么只需要对所有合法的
比较难处理的条件是
我们可以求出每个连续段的 OGF
因此我们要求出所有满足
不难用分治 NTT 维护,用
转移时排除
求出最终的
我们还要求
答案就是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
namespace P {
const int MOD=998244353,N=1<<19,G=3;
int rev[N],inv[N],fac[N],ifac[N],w[N<<1];
int ksm(int a,int b=MOD-2) {
int ret=1;
for(;b;a=1ll*a*a%MOD,b=b>>1) if(b&1) ret=1ll*ret*a%MOD;
return ret;
}
void poly_init() {
inv[1]=1;
for(int i=2;i<N;++i) inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[0]=ifac[0]=1;
for(int i=1;i<N;++i) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*inv[i]%MOD;
for(int k=1;k<=N;k<<=1) {
int x=ksm(G,(MOD-1)/k); w[k]=1;
for(int i=1;i<k;++i) w[i+k]=1ll*x*w[i+k-1]%MOD;
}
}
int plen(int x) { int y=1; for(;y<x;y<<=1); return y; }
void ntt(int *f,bool idft,int n) {
for(int i=0;i<n;++i) {
rev[i]=(rev[i>>1]>>1);
if(i&1) rev[i]|=n>>1;
}
for(int i=0;i<n;++i) if(rev[i]<i) swap(f[i],f[rev[i]]);
for(int k=2,x,y;k<=n;k<<=1) {
for(int i=0;i<n;i+=k) {
for(int j=i;j<i+k/2;++j) {
x=f[j],y=1ll*f[j+k/2]*w[k+j-i]%MOD;
f[j]=(x+y>=MOD)?x+y-MOD:x+y,f[j+k/2]=(x>=y)?x-y:x+MOD-y;
}
}
}
if(idft) {
reverse(f+1,f+n);
for(int i=0,x=ksm(n);i<n;++i) f[i]=1ll*f[i]*x%MOD;
}
}
void poly_inv(const int *f,int *g,int n) {
static int a[N];
g[0]=ksm(f[0]);
int k=2;
for(;k<(n<<1);k<<=1) {
for(int i=0;i<k;++i) a[i]=f[i];
ntt(g,0,k<<1),ntt(a,0,k<<1);
for(int i=0;i<(k<<1);++i) {
g[i]=(2-1ll*a[i]*g[i]%MOD)*g[i]%MOD;
if(g[i]<0) g[i]+=MOD;
}
ntt(g,1,k<<1);
memset(g+k,0,sizeof(int)*k);
}
memset(g+n,0,sizeof(int)*(k-n));
memset(a,0,sizeof(int)*k);
}
}
using P::ntt;
const int N=1<<19,MOD=998244353;
vector <int> f[N][2][2];
int n,k,a[2][2][N],b[2][2][N],c[N];
void cdq(int l,int r) {
if(l==r) {
f[l][0][0]={1,0};
f[l][0][1]={0,0};
f[l][1][0]={0,0};
f[l][1][1]={0,l};
return ;
}
int mid=(l+r)>>1,len=P::plen(r-l+2);
cdq(l,mid),cdq(mid+1,r);
for(int x:{0,1}) for(int y:{0,1}) {
memset(a[x][y],0,sizeof(int)*len);
memset(b[x][y],0,sizeof(int)*len);
for(int i=0;i<=mid-l+1;++i) a[x][y][i]=f[l][x][y][i];
for(int i=0;i<=r-mid;++i) b[x][y][i]=f[mid+1][x][y][i];
ntt(a[x][y],0,len),ntt(b[x][y],0,len);
}
for(int x:{0,1}) for(int y:{0,1}) {
memset(c,0,sizeof(int)*len);
for(int p:{0,1}) for(int q:{0,1}) if(!p||!q) {
for(int i=0;i<len;++i) c[i]=(c[i]+1ll*a[x][p][i]*b[q][y][i])%MOD;
}
ntt(c,1,len);
f[l][x][y].resize(r-l+2);
for(int i=0;i<=r-l+1;++i) f[l][x][y][i]=c[i];
}
}
int u[N],v[N],w[N];
signed main() {
scanf("%d%d",&n,&k),P::poly_init();
cdq(1,k);
for(int i=1;i<=k;++i) {
for(int x:{0,1}) for(int y:{0,1}) {
u[i]=(u[i]+f[1][x][y][i])%MOD;
if(x) v[i]=(v[i]+f[1][x][y][i])%MOD;
}
if(i&1) u[i]=(MOD-u[i])%MOD;
else v[i]=(MOD-v[i])%MOD;
}
++u[0];
P::poly_inv(u,w,n+1);
int ans=0;
for(int i=0;i<=n;++i) ans=(ans+1ll*v[i]*w[n-i])%MOD;
for(int i=1;i<=n;++i) ans=1ll*ans*P::inv[2*i-1]%MOD;
printf("%d\n",ans);
return 0;
}
Round #16 - 2024.10.4
A. [AGC055C] Delete
题目大意
对于一个排列
,定义 表示 的 LIS,求共能生成有多少个值域 的序列 。 数据范围:
。
思路分析
很显然
先考虑
很显然此时 LIS 长度
并且这样的序列总能构造,一组合法的构造形如:
那么对于一般的情况,会有一些
设这些必经点为
那么一个必要条件就是
不妨设
那么我们只要枚举
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e4+5;
int n,m,MOD;
ll fac[MAXN],ifac[MAXN];
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll C(int x,int y) {
if(x<0||y<0||y>x) return 0;
return fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
signed main() {
scanf("%d%d%d",&n,&m,&MOD);
for(int i=fac[0]=ifac[0]=1;i<MAXN;++i) ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
ll ans=1;
if(n>3&&(m==n-1)) ++ans;
for(int k=0;k<=n;++k) for(int s=0;2*s+k<=n;++s) {
ans=(ans+C(s+k,k)*C(k+1,n-k-2*s)%MOD*max(min(m,k+s)-max(k,3)+1,0))%MOD;
}
printf("%lld\n",ans);
return 0;
}
B. [AGC053C] Stack
题目大意
给定
,所有元素是 的一个随机排列,每次操作选定一个 ,比较 的大小:
- 如果
那么删除 并把 平移到 。 - 否则删除
并做相同操作。 求清空其中一个序列的最小操作次数的期望。
数据范围:
。
思路分析
先考虑如何刻画最小操作次数,不妨设
考虑每个
因此答案有下界
否则找到第一个
因此答案就是
考虑什么时候
并且对于
因此
概率最终要
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e6+5,MOD=1e9+7;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
int n;
ll fac[MAXN],ifac[MAXN];
signed main() {
scanf("%d",&n);
fac[0]=ifac[0]=fac[1]=ifac[1]=1;
for(int i=2;i<MAXN;++i) ifac[i]=ksm(fac[i]=i*fac[i-2]%MOD);
ll ans=2*n;
for(int k=0;k<n;++k) {
//prod 1-1/min(i+k,n)+i
//i=1~n-k: 2i+k-1/2i+k, else n+i-1/n+i
ll w=(2*n-k)*ksm(2*n)%MOD*fac[2*n-k-1]%MOD*ifac[2*n-k]%MOD;
if(k) w=w*ifac[k-1]%MOD*fac[k]%MOD;
ans=(ans+2*(MOD-w))%MOD;
}
printf("%lld\n",ans);
return 0;
}
*C. [AGC057D] Sum
题目大意
给定
,定义一个 的子集 是好的当且仅当不能从 中选出若干元素求和得到 (可以重复选元素)。 取所有好的集合中最大的一个,有多个取从小到大排序后字典序最小的集合
,求出其中的第 个元素。 数据范围:
。
思路分析
显然集合最大大小就是
不妨设
我们发现如果
其次,如果
考虑反证法,如果存在一种选元素的方法选了
中元素,显然至多选一个,因为选两个的和就 了。 那么设选出了
,则 中能选出若干元素和为 ,但 且能被 中元素表示,则根据刚才的结论,一定有 ,从而导出矛盾。
那么求
考虑
那么加入
加入某个
设
加入一个
一个元素加入后合法当且仅当加入后
枚举每个
求答案可以二分,求出
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll inf=1e18;
ll n,m,p,f[45];
ll cnt(ll x) {
ll s=0;
for(int i=0;i<p;++i) if(f[i]<=x) s+=(x-f[i])/p+(i>0);
return s;
}
void solve() {
scanf("%lld%lld",&n,&m);
if(m>(n-1)/2) return puts("-1"),void();
for(p=1;n%p==0;++p);
f[0]=0,fill(f+1,f+p,inf);
while(true) {
ll v=inf;
for(int x=1;x<p;++x) {
ll c=0;
for(int i=1;i<p;++i) c=max(c,(n-f[(n+(p-x)*i)%p])/i+1);
while(c%p!=x) ++c;
if(c<f[x]) v=min(v,c);
}
if(v>(n-1)/2) break;
f[v%p]=v;
for(int r=0;r<__gcd(v,p);++r) {
for(int i=r,c=0;c<2;i=(i+v)%p,c+=(i==r)) {
f[(i+v)%p]=min(f[(i+v)%p],f[i]+v);
}
}
}
if(m<=cnt((n-1)/2)) {
ll l=1,r=(n-1)/2,z=(n-1)/2;
while(l<=r) {
ll mid=(l+r)>>1;
if(cnt(mid)>=m) z=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",z);
} else {
ll l=(n-1)/2+1,r=n-1,z=n-1;
while(l<=r) {
ll mid=(l+r)>>1;
if((n-1)/2-(n-mid-1-cnt(n-mid-1))>=m) z=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",z);
}
}
signed main() {
int T; scanf("%d",&T);
while(T--) solve();
return 0;
}
*D. [AGC056D] Game
题目大意
给定
,两个人 A 和 B 轮流取数,最终如果 A 取出的数总和 就胜利,问谁有必胜策略。 数据范围:
, 为偶数。
思路分析
先考虑
先考虑 B 能把取出的数最小化到什么程度,我们发现 A 可以选定一组匹配,B 选其中的某个元素,A 就选其匹配点,那么 A 最终能得到每组匹配中较小的数。
为了最大化这个数,A 肯定会把排序后相邻的元素结成匹配,即得到的最小值就是排序后奇数位上的值,这个和
如果 B 想要最大化取出的数,那么 A 依然会选定匹配并取出每组匹配中较大的数,此时 A 的最优方案依然是把排序后相邻的元素结成匹配,得到排序后偶数位上的值,总和
回到原问题。
此时枚举 A 第一步的操作,那么会剩余奇数个元素并轮到 B 先手。
A 依然考虑把元素进行匹配,但是此时两两匹配后会在剩余一个元素在最后一次操作给 B。
并且这个元素也是由 A 控制的,枚举这个元素后再判定排序后奇数位和偶数位上的和是否合法即可。
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5005;
ll L,R,a[MAXN],b[MAXN];
signed main() {
int n;
scanf("%d%lld%lld",&n,&L,&R);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
sort(a+1,a+n+1);
for(int i=1;i<=n;++i) {
for(int j=1;j<=n;++j) if(i^j) b[j-(j>i)]=a[j];
array <ll,2> sl{0,0},sr{0,0};
for(int j=1;j<n;++j) sr[j&1]+=b[j];
for(int j=1;j<n;++j) {
sr[j&1]-=b[j];
if(L<=sl[1]+sr[0]+a[i]&&sl[0]+sr[1]+a[i]<=R) return puts("Alice"),0;
sl[j&1]+=b[j];
}
}
puts("Bob");
return 0;
}
*E. [AGC056E] Ball
题目大意
给定
个洞排成一圈,位置为 ,随机 次,每次取出一个球,以 的概率放在 上,然后从 出发,每到一个还没有球洞口,就以 的概率放进这个洞口,否则继续考虑下一个洞口。 对于每个洞口
,求最后没有球的洞口恰好是 的概率。 数据范围:
。
思路分析
用
显然直接用 dp 维护这个判定过程是很难的,考虑重排这些判定事件。
首先我们要求
那么我们只能考虑交换
因此我们可以按
很显然我们只要求出第
我们只需要处理
此时所有球一起判定比较不优,考虑把判定顺序换回先判定
对于
并且
那么对于
因此我们只要求出还剩
考虑 dp,设
那么转移时先加入若干
然后要考虑是否存在某个
因此有转移
最终答案是
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=45,MOD=998244353,inv=828542813;
ll ksm(ll a,ll b=MOD-2) { ll s=1; for(;b;a=a*a%MOD,b>>=1) if(b&1) s=s*a%MOD; return s; }
ll f[MAXN][MAXN],g[MAXN][MAXN],a[MAXN],pw[MAXN],fac[MAXN],ifac[MAXN];
int n;
inline void add(ll &x,const ll &y) { x=(x+y)%MOD; }
ll solve() {
memset(f,0,sizeof(f)),f[0][0]=1;
for(int i=1;i<=n;++i) {
memcpy(g,f,sizeof(g)),memset(f,0,sizeof(f));
for(int j=0;j<n;++j) for(int k=0;k<=j;++k) {
ll pr=1;
for(int x=0;j+x<n;++x) {
add(f[j+x][k+x],g[j][k]*pr%MOD*ifac[x]);
pr=pr*a[i]%MOD;
}
}
if(i==n) break;
memcpy(g,f,sizeof(g)),memset(f,0,sizeof(f));
for(int j=0;j<n;++j) for(int k=0;k<=j;++k) {
ll pr=ksm(pw[k]);
add(f[j][k],g[j][k]*pr);
if(k) add(f[j][k-1],g[j][k]*(1+MOD-pr));
}
}
ll s=0;
for(int i=0;i<n;++i) add(s,f[n-1][i]*fac[n-1]%MOD*ksm(pw[i+1]-1));
return s;
}
signed main() {
for(int i=pw[0]=fac[0]=ifac[0]=1;i<MAXN;++i) {
pw[i]=pw[i-1]*2%MOD,ifac[i]=ksm(fac[i]=fac[i-1]*i%MOD);
}
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),a[i]=a[i]*inv%MOD;
for(int i=1;i<=n;++i) rotate(a+1,a+2,a+n+1),printf("%lld ",solve());
puts("");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具