【学习笔记】AGC046

C - Shift

首先问题转化为,给定一个串 t t t,问 s s s经过最少多少步能变成 t t t

然后考虑逆向操作,每次相当于把一个 0 0 0前面的 1 1 1移动到后面去

c i c_i ci表示第 i i i 0 0 0和第 i + 1 i+1 i+1 0 0 0之间的 1 1 1的数目,显然两个串相同等价于 { c i } \{c_i\} {ci}相同,并且每次操作相当于选择 i < j i<j i<j,使得 c i c_i ci 1 1 1 c j c_j cj 1 1 1

s i = ∑ a i s_i=\sum a_i si=ai,显然答案是 ∑ max ⁡ ( 0 , c i − c i ′ ) \sum \max(0,c_i-c'_i) max(0,cici),并且对任意 i i i满足 s i ≥ s i ′ s_i\ge s'_i sisi

注意两维的大小都是 n n n,如果暴力转移那么复杂度似乎是 O ( n 4 ) O(n^4) O(n4)

事实上跑不满。使用部分和可以优化到 O ( n 3 ) O(n^3) O(n3)

复杂度都不会算是不是该退役了

能过的代码为什么要优化呀

#include<bits/stdc++.h> #define ll long long #define pii pair<int,int> #define fi first #define se second #define pb push_back #define db long double #define cpx complex<db> #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int mod=998244353; string s; int n,m,K,a[305],f[305][305],g[305][305],one,res; void add(int &x,int y){ if((x+=y)>=mod)x-=mod; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>s>>K,n=s.size(); for(int i=0;i<n;i++){ if(s[i]=='0'){ m++;for(int j=i-1;j>=0;j--){ if(s[j]=='1')a[m]++; else break; } one+=a[m]; } }m++;for(int i=n-1;i>=0;i--){ if(s[i]=='1')a[m]++; else break; }one+=a[m]; int s=0;f[0][0]=1; for(int i=1;i<=m;i++){ memset(g,0,sizeof g); for(int j=s;j<=one;j++){ for(int k=0;k<=j;k++){ if(!f[j][k])continue; for(int l=0;l<=one-j;l++){ add(g[j+l][k+max(0,l-a[i])],f[j][k]); } } }memcpy(f,g,sizeof g),s+=a[i]; }for(int i=0;i<=min(one,K);i++)add(res,f[one][i]); cout<<res; }

D - Secret Passage

首先考虑长度为 m m m的串 t t t能否被生成。考虑逆向操作,每次相当于选一个数放到开头,然后在开头放 0 / 1 0/1 0/1。那么考虑没被操作过的位置肯定构成了 s s s的后缀,被操作的位置可以按任意顺序排列,那么只要判一下 0 / 1 0/1 0/1的数目。

f i , j , k f_{i,j,k} fi,j,k表示生成了长度为 i i i的后缀,其中匹配的长度是 j j j,未匹配的位置中 1 1 1的个数是 k k k的方案数。大力转移即可。

诶怎么一直wa???

仔细思考 wa了半天 后发现构造并没有那么简单,但是我们发现此时正着非常好做(因为插到后面就不用管了),这里有一个非常巧妙的观察:第一次显然对前两个数进行操作,如果剩下的那个数没有放在后面,那么它在以后的操作中也不会被放在后面,设 h i , j , k h_{i,j,k} hi,j,k表示前缀 i i i位生成了 j j j 0 0 0 k k k 1 1 1时可以被任意放置的数的最大值,可以 O ( 1 ) O(1) O(1)转移。

复杂度 O ( n 3 ) O(n^3) O(n3)

细节很多,比较难受

#include<bits/stdc++.h> #define ll long long #define pii pair<int,int> #define fi first #define se second #define pb push_back #define db long double #define cpx complex<db> #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int mod=998244353; int n,a[305],b[305],f[305][305],g[305][305],h[305][305][305],res; char s[305]; void add(int &x,int y){ if((x+=y)>=mod)x-=mod; } void chmax(int &x,int y){ x=max(x,y); } signed main(){ scanf("%s",s+1),n=strlen(s+1); for(int i=1;i<=n;i++)a[i]=(s[i]=='1'),b[i]=b[i-1]+a[i]; memset(h,-1,sizeof h);h[0][0][0]=0; for(int i=0;i<n;i++){ for(int j=0;j<=i;j++){ for(int k=0;k<=i;k++){ if(h[i][j][k]==-1)continue; if(a[i+1])chmax(h[i+1][j+1][k],h[i][j][k]-1); if(!a[i+1])chmax(h[i+1][j][k+1],h[i][j][k]-1); chmax(h[i+1][j][k],h[i][j][k]); if(i+2<=n){ chmax(h[i+2][j][k],h[i][j][k]+1); if(a[i+1]||a[i+2])chmax(h[i+2][j+1][k],h[i][j][k]); if(!a[i+1]||!a[i+2])chmax(h[i+2][j][k+1],h[i][j][k]); } } } } f[0][0]=1; for(int i=1;i<=n;i++){ memset(g,0,sizeof g); for(int j=0;j<i;j++){ for(int k=0;k<i;k++){ if(!f[j][k])continue; add(g[j][k+(!a[n-j])],f[j][k]); add(g[j+1][k],f[j][k]); } }memcpy(f,g,sizeof g); for(int j=0;j<=i;j++){ for(int k=0;k<=i;k++){ if(f[j][k]&&2*i-j<=n&&k<=b[n-j]&&i-j-k<=n-j-b[n-j]&&h[n-j][k][i-j-k]!=-1){ add(res,f[j][k]); } } } }cout<<res; }

E - Permutation Cover

考虑两件事:
1.1 1.1 1.1 如何判断是否有解。这等价于是找必要条件,我们任取 i i i, j j j比较它们的出现次数,容易得到结论: 2 min ⁡ ( a i , a j ) ≥ max ⁡ ( a i , a j ) 2\min(a_i,a_j)\ge \max(a_i,a_j) 2min(ai,aj)max(ai,aj),构造也非常简单,让最小数 − 1 -1 1,其他数 − 2 -2 2即可。
1.2 1.2 1.2 如何求字典序最小。换句话说,给定一段前缀,判断是否有解。先将不足 K K K的部分补完,显然此时如果 2 min ⁡ ( a i ) ≥ max ⁡ ( a i ) 2\min(a_i)\ge \max(a_i) 2min(ai)max(ai)那么有解,否则一定满足 2 min ⁡ ( a i ) + 1 = max ⁡ ( a i ) 2\min(a_i)+1=\max(a_i) 2min(ai)+1=max(ai),此时必须再补一个最大值在后面,这样恰好有解。

复杂度 O ( ∑ a i k 2 ) O(\sum a_ik^2) O(aik2)

代码很难写

#include<bits/stdc++.h> #define ll long long #define pii pair<int,int> #define fi first #define se second #define pb push_back #define db long double #define cpx complex<db> #define inf 0x3f3f3f3f using namespace std; int K,n,m,a[1005],b[1005],s[1005],v[1005]; int check(int n){ for(int i=1;i<=K;i++)v[i]=0; for(int i=n-K+1;i<=n;i++)v[s[i]]=1; for(int i=1;i<=K;i++)if(!v[i])return 0; return 1; } int solve(){ int t=1;while(s[t])t++;for(int i=1;i<=K;i++)b[i]=a[i]; for(int i=1;i<=K;i++)v[i]=0; for(int i=m+1;i<t;i++){ if(v[s[i]])return 0; v[s[i]]=1; }for(int i=m+1;i<t;i++)b[s[i]]++;for(int i=1;i<=K;i++)b[i]--; int mi=inf,mx=0,L=0;for(int i=1;i<=K;i++)mi=min(mi,b[i]),mx=max(mx,b[i]); for(int i=1;i<=K;i++)if(b[i]==mx)L++; if(2*mi>=mx)return 1; if(2*mi==mx-1){ for(int i=m+1;i<t;i++){ if(b[s[i]]==mi)return 0; if(b[s[i]]==mx)L--; if(!L)return 1; }return 1; }for(int i=1;i<=K;i++)b[i]=a[i]; if(m){ for(int i=m-K+1;i<=m;i++)v[s[i]]=i-m+K; int l=0;for(int i=m+1;i<t;i++)l=max(l,v[s[i]]),b[s[i]]++; for(int i=m-K+1;i<=m-K+l;i++)b[s[i]]--; mi=inf,mx=0,L=0;for(int i=1;i<=K;i++)mi=min(mi,b[i]),mx=max(mx,b[i]); for(int i=1;i<=K;i++)if(b[i]==mx)L++; if(2*mi>=mx)return 1; if(2*mi==mx-1){ if(l!=K){ for(int i=m-K+l+1;i<=m;i++){ if(b[s[i]]==mi)return 0; if(b[s[i]]==mx)L--; if(!L)return 1; }return 1; } else { for(int i=m+1;i<t;i++){ if(b[s[i]]==mi)return 0; if(b[s[i]]==mx)L--; if(!L)return 1; }return 1; } }return 0; }for(int i=1;i<t;i++)b[s[i]]++;for(int i=1;i<=K;i++)b[i]--; mi=inf,mx=0,L=0;for(int i=1;i<=K;i++)mi=min(mi,b[i]),mx=max(mx,b[i]); for(int i=1;i<=K;i++)if(b[i]==mx)L++; if(2*mi>=mx)return 1; if(2*mi==mx-1){ for(int i=1;i<t;i++){ if(b[s[i]]==mi)return 0; if(b[s[i]]==mx)L--; if(!L)return 1; }return 1; } return 0; } signed main(){ cin>>K;for(int i=1;i<=K;i++)cin>>a[i],n+=a[i]; if(!solve()){ cout<<-1; return 0; } for(int i=1;i<=n;i++){ int ok=0; for(int j=1;j<=K;j++){ if(a[j]==0)continue; s[i]=j,a[j]--; if(solve()){ ok=1; break; } a[j]++; }assert(ok); if(i>=K&&check(i))m=i; cout<<s[i]<<' '; } }

F - Forbidden Tournament

这是道数学题。

考虑怎么判断一张图是否合法。

如果存在一个点没有入度,那么把这个点删去,直到每个点至少有一个入度。

我们称 H H H是不好的,也就是三元环都向一个点连边。

任选一个点 v v v,记 Y = { w ∣ v → w } Y=\{w|v\to w\} Y={wvw},也就是 v v v出边连向的集合, X = V ∖ Y X=V\setminus Y X=VY,也就是 v v v的入边加上 v v v自己。

如果 X X X中有环,也就是有三元环,首先 v v v肯定不在环内,那么这个环都向 v v v连边,就构成了 H H H

1.1 1.1 1.1 X X X是一个 D A G DAG DAG,存在一个 x 1 , . . . , x k x_1,...,x_k x1,...,xk x k = v x_k=v xk=v,满足若 i < j i<j i<j,则 x i → x j x_i\to x_j xixj

1.2 1.2 1.2 对于 u ∈ X u\in X uX w ∈ Y w\in Y wY,如果 w → u w\to u wu,且 w → w ′ w\to w' ww,我们可以推出 w ′ → u w'\to u wu,否则会构成 H H H

在这里插入图片描述
1.3 1.3 1.3 如果 Y Y Y中有环,那么取 u = x 1 u=x_1 u=x1,设环外一点 w w w满足 w → u w\to u wu,不难发现两种情况都会构成 H H H,因此 Y Y Y是一个 D A G DAG DAG,存在 y 1 , . . . , y l y_1,...,y_l y1,...,yl,满足若 i < j i<j i<j,则 y i → y j y_i\to y_j yiyj

1.4 1.4 1.4 不难发现 y l → x 1 y_l\to x_1 ylx1。我们将 y l y_l yl提出来,得到:若 x i → y l x_i\to y_l xiyl,那么 ∀ i ′ ∈ [ i , k ] \forall i'\in [i,k] i[i,k] x i ′ → y l x_{i'}\to y_l xiyl

请添加图片描述

1.5 1.5 1.5 我们不妨取最大的 t t t满足 y l → x t y_l\to x_t ylxt。设 i ∈ [ 1 , t ] i\in [1,t] i[1,t],我们再任取一个点 y j y_j yj,若 x i → y j x_i\to y_j xiyj,那么 ∀ i ′ ∈ [ i , t ] \forall i'\in [i,t] i[i,t] x i ′ → y j x_i'\to y_j xiyj。注意到 i ∈ [ t + 1 , k ] i\in [t+1,k] i[t+1,k] x i → y j x_i\to y_j xiyj,因此 t t t的范围可以省去。

基于上述观察,我们可以直接计数。设删去了 i i i个入度为 0 0 0的点,那么转化为 ( N − i , K − i ) (N-i,K-i) (Ni,Ki)规模的问题,乘上系数 ( N i ) i ! \binom{N}{i}i! (iN)i!。枚举左部点和右部点的数目,问题转化为一个 k × l k\times l k×l矩阵的方案数,其中 a i , j = 1 a_{i,j}=1 ai,j=1表示 y j → x i y_j\to x_i yjxi

我们将性质 1.2 1.2 1.2转化成:若 y j → x i y_j\to x_i yjxi,那么 ∀ j ′ ∈ [ j , l ] \forall j'\in [j,l] j[j,l] y j ′ → x i y_j'\to x_i yjxi。那么结合 1.2 1.2 1.2 1.5 1.5 1.5,这个网格图满足:

  • a i , j = 1 ⇒ ∀ j ′ ∈ [ j , l ] , a i , j ′ = 1 a_{i,j}=1\Rightarrow \forall j'\in [j,l],a_{i,j'}=1 ai,j=1j[j,l],ai,j=1
  • a i , j = 0 ⇒ ∀ i ′ ∈ [ i , k ] , a i ′ , j = 1 a_{i,j}=0\Rightarrow \forall i'\in [i,k],a_{i',j}=1 ai,j=0i[i,k],ai,j=1
  • a 1 , l = 1 a_{1,l}=1 a1,l=1
  • a k , j = 1 a_{k,j}=1 ak,j=1

直接对这个阶梯状矩阵计数即可。不难发现只需要判边界处的点的度数。

这一步复杂度 O ( k l ) O(kl) O(kl),总复杂度 O ( n 4 ) O(n^4) O(n4)

似乎这道题有什么高论但是我不清楚

#include<bits/stdc++.h> #define ll long long #define pii pair<int,int> #define fi first #define se second #define pb push_back #define db long double #define cpx complex<db> #define inf 0x3f3f3f3f using namespace std; int n,K,mod; ll fac[205],inv[205],f[205],res; ll pw(ll x,ll y=mod-2){ ll z(1); for(;y;y>>=1){ if(y&1)z=z*x%mod; x=x*x%mod; }return z; } void init(int n){ fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod; inv[n]=pw(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod; } ll binom(int x,int y){ return fac[x]*inv[y]%mod*inv[x-y]%mod; } ll calc(int n,int m,int K){ assert(K>=n-1); if(m==0)return 0; memset(f,0,sizeof f),f[1]=1; for(int i=1;i<=m;i++){ ll sm=0; for(int j=1;j<=n;j++){ if(i==1&&j==n)continue; sm=(sm+f[j])%mod; if(m-i+j<=K&&i+n-j-1<=K)f[j]=sm; else f[j]=0; } }ll res=0;for(int i=1;i<=n;i++)res=(res+f[i])%mod; return res; } ll solve(int n,int m){ if(n==1)return 1; ll res(0); for(int i=2;i<=m+1;i++)res=(res+calc(i,n-i,m))%mod; res=res*fac[n-1]%mod; return res; } signed main(){ cin>>n>>K>>mod,init(n); for(int i=0;i<=K;i++){ res=(res+binom(n,i)*fac[i]%mod*solve(n-i,K-i))%mod; } cout<<(res+mod)%mod; }

__EOF__

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