2021.9.21考试总结[NOIP模拟58]
T1 lesson5!
开始以为是个无向图,直接不懂,跳去T2了。
之后有看了一眼发现可暴力,于是有了\(80pts\)。
发现这个图是有拓扑序的,于是可以用拓扑排序找最长路径。先找原图内在最长路径上的点,挨个删了跑拓扑排,看哪个最短。
正解太nb了待补。
\(code:\)
80pts
#include<bits/stdc++.h>
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=(1<<4)+(1<<5)+x%10; x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int& x,int& y){ x^=y^=x^=y; }
inline void ckmax(int& x,int y){ x=x<y?y:x; }
inline void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=1e5+5,MM=5e5+5;
int t,n,m,mx,ans,pos,ban,idx,to[MM],nex[MM],head[NN],dis[NN],pre[NN];
int l,r,q[NN],in[NN],deg[NN];
bool vis[NN];
vector<int>vec;
inline void add(int a,int b){
to[++idx]=b; nex[idx]=head[a]; head[a]=idx; ++in[b];
}
void topo(){
l=1; r=0;
for(int i=1;i<=n;i++)
if(!in[i]&&ban!=i) pre[i]=0, dis[i]=0, q[++r]=i;
while(l<=r){
int x=q[l++];
if(dis[x]>mx) mx=dis[x];
for(int i=head[x];i;i=nex[i]) if(in[to[i]]&&ban!=to[i]){
--in[to[i]];
if(!in[to[i]]){
pre[to[i]]=x;
dis[to[i]]=dis[x]+1;
q[++r]=to[i];
}
}
}
}
signed main(){
FILE *R=freopen("johnny.in","r",stdin);
FILE *W=freopen("johnny.out","w",stdout);
t=read();
while(t--){
n=read(); m=read();
mx=ban=idx=0; vec.clear();
for(int i=1;i<=n;i++) in[i]=head[i]=vis[i]=0;
for(int a,b,i=1;i<=m;i++)
a=read(),b=read(), add(a,b);
for(int i=1;i<=n;i++) deg[i]=in[i];
topo(); ans=mx; pos=INT_MAX;
for(int i=1;i<=n;i++) if(dis[i]==mx){
int x=i;
while(x) vec.push_back(x), x=pre[x];
}
for(int i=0;i<vec.size();i++) if(!vis[vec[i]]){
ban=vec[i]; mx=0; vis[ban]=1;
for(int j=1;j<=n;j++) in[j]=deg[j];
for(int j=head[ban];j;j=nex[j]) --in[to[j]];
topo();
if(mx<ans||(mx==ans&&ban<pos)) ans=mx, pos=ban;
}
write(pos,' '); write(ans,'\n');
}
}
T2 贝尔数
模数不是质数就很搞。。
发现分解质因数后模数是五个一次的两位质数相乘,题目还给了模质数意义下的同余公式,于是可以先求出模五个质数意义下这一位贝尔数的值,然后再用中国剩余定理解个同余方程合并即可。
对每个质数可以先预处理前四十多个贝尔数,然后矩阵加速递推。
个人认为挺神的题(主要是数学跟矩阵都太菜了
\(code:\)
T2
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=(1<<4)+(1<<5)+x%10; x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int& x,int& y){ x^=y^=x^=y; }
inline void ckmax(int& x,int y){ x=x<y?y:x; }
inline void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=1010,mod=95041567;
int T,n,nul,c[50][50],bell[50],pre[6],calc[6];
int pri[6]={0,31,37,41,43,47};
namespace Crt{
int exgcd(int a,int b,int& x,int& y){
if(!b){
x=1; y=0;
return a;
}
int g=exgcd(b,a%b,x,y),z;
z=y; y=x-a/b*y; x=z;
return g;
}
void init(){
bell[0]=1;
for(int i=0;i<50;i++) c[i][0]=1;
for(int i=1;i<50;i++) for(int j=1;j<=i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
for(int i=1;i<50;i++) for(int j=0;j< i;j++)
(bell[i]+=c[i-1][j]*bell[j]%mod)%=mod;
for(int i=1;i<6;i++){
int g=exgcd(pri[i],mod/pri[i],nul,pre[i]);
pre[i]=(pre[i]%mod+mod)%mod; pre[i]=(mod/pri[i])*pre[i]%mod;
}
}
int solve(){
int res=0;
for(int i=1;i<6;i++) (res+=pre[i]*calc[i])%=mod;
return res;
}
} using namespace Crt;
namespace Matrix{
struct matrix{
int s[50][50];
void clr(){ memset(s,0,sizeof(s)); }
void pre(){ clr(); for(int i=0;i<50;i++) s[i][i]=1; }
void print(){for(int i=0;i<50;++i)for(int j=0;j<50;++j)write(s[i][j],j==49?'\n':' ');}
}mat[6],base[6];
matrix mul(matrix x,matrix y,int z){
matrix res; res.clr();
for(int i=0;i<50;i++)
for(int k=0;k<50;k++)
for(int j=0;j<50;j++)
(res.s[i][j]+=x.s[i][k]*y.s[k][j]%z)%=z;
return res;
}
void qpow(matrix& a,matrix b,int c,int d){
while(c){
if(c&1) a=mul(a,b,d);
b=mul(b,b,d);
c>>=1;
}
}
void prework(){
for(int i=1;i<6;i++){
for(int j=0;j<pri[i];j++) mat[i].s[1][j]=bell[j]%pri[i];
base[i].pre();
base[i].s[1][0]=base[i].s[0][pri[i]-1]=base[i].s[1][pri[i]-1]=1;
for(int j=1;j<pri[i]-1;j++) base[i].s[j+1][j]=1;
}
}
} using namespace Matrix;
signed main(){
FILE *R=freopen("bell.in","r",stdin);
FILE *W=freopen("bell.out","w",stdout);
T=read(); init();
while(T--){
n=read(); prework();
for(int i=1;i<6;i++){
qpow(mat[i],base[i],n/pri[i],pri[i]);
calc[i]=mat[i].s[1][n%pri[i]];
}
write(solve(),'\n');
}
return 0;
}
T3 穿越广场
考完后发现这好像是个AC自动机套路题,但奈何时间久远,学的时候写的太快没多思考总结,于是SB了。
设\(f_{i,j,k,l}\)为\(DP\)到第\(i\)位,填了\(j\)个\(R\),在自动机中走到第\(k\)个节点,结束状态为\(l\)的方案数。
\(l\)为二进制状态,有两位。
于是有
\(\huge{f_{i,j,k,l} \to f_{i+1,j,to[k]['D'],l|end[to[k]['D']]}}\)
\(\huge{f_{i,j,k,l} \to f_{i+1,j+1,to[k]['R'],l|end[to[k]['R']]}}\)
初始状态\(f_{0,0,1,0}=1\)。
注意每个点继承它\(fail\)的状态即可。
\(code:\)
T3
#include<bits/stdc++.h>
#define int long long
#define ULL unsigned long long
using namespace std;
namespace IO{
inline int read(){
char ch=getchar(); int x=0,f=1;
while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
inline void write(int x,char sp){
char ch[20]; int len=0;
if(x<0){ putchar('-'); x=~x+1; }
do{ ch[len++]=(1<<4)+(1<<5)+x%10; x/=10; }while(x);
for(int i=len-1;~i;--i) putchar(ch[i]); putchar(sp);
}
inline int max(int x,int y){ return x<y?y:x; }
inline int min(int x,int y){ return x<y?x:y; }
inline void swap(int& x,int& y){ x^=y^=x^=y; }
inline void ckmax(int& x,int y){ x=x<y?y:x; }
inline void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;
const int NN=110,p=1e9+7;
int t,n,m,ext,ans,f[NN<<1][NN][NN<<1][4];
char ch[NN];
namespace AC_automaton{
int root,tot,to[NN<<1][2],fail[NN<<1],end[NN<<1];
void insert(char *s){
int len=strlen(s),u=1;
for(int i=0;i<len;i++){
int now=(s[i]=='R');
if(!to[u][now]) to[u][now]=++tot, end[tot]=0;
u=to[u][now];
}
end[u]=root?2:1;
if(!root) root=1;
}
void build(){
queue<int>q;
if(to[root][0]) q.push(to[root][0]), fail[to[root][0]]=root;
else to[root][0]=root;
if(to[root][1]) q.push(to[root][1]), fail[to[root][1]]=root;
else to[root][1]=root;
while(!q.empty()){
int u=q.front(); q.pop();
end[u]|=end[fail[u]];
if(to[u][0]) fail[to[u][0]]=to[fail[u]][0], q.push(to[u][0]);
else to[u][0]=to[fail[u]][0];
if(to[u][1]) fail[to[u][1]]=to[fail[u]][1], q.push(to[u][1]);
else to[u][1]=to[fail[u]][1];
}
}
} using namespace AC_automaton;
signed main(){
FILE *R=freopen("square.in","r",stdin);
FILE *W=freopen("square.out","w",stdout);
t=read();
while(t--){
m=read(); n=read(); tot=1; root=0; ext=n+m; ans=0;
memset(f,0,sizeof(f));
memset(to,0,sizeof(to));
memset(fail,0,sizeof(fail));
scanf("%s",ch); insert(ch);
scanf("%s",ch); insert(ch);
build(); f[0][0][1][0]=1;
for(int i=0;i<ext;i++)
for(int j=0;j<=m;j++){
if(j>i||i-j>n) continue;
for(int k=1;k<=tot;k++)
for(int u=0;u<4;u++){
(f[i+1][j ][to[k][0]][u|end[to[k][0]]]+=f[i][j][k][u])%=p;
(f[i+1][j+1][to[k][1]][u|end[to[k][1]]]+=f[i][j][k][u])%=p;
}
}
for(int i=1;i<=tot;i++) (ans+=f[ext][m][i][3])%=p;
write(ans,'\n');
}
return 0;
}
T4 舞动的夜晚
先跑最大流,然后在残量网络上跑\(tarjan\),如果边在\(SCC\)里说明它没有影响。
改的有点ex,待补