2021.9.30 Codeforces 中档题四道
Codeforces 1528D It's a bird! No, it's a plane! No, it's AaParsa!(*2500)
考虑以每个点为源点跑一遍最短路,每次取出当前距离最小的点然后更新一圈周围的点,具体更新方法是:设 \(p\) 为当前这一轮我们取出的点,那么显然对于一条本来由 \(p\to q\),权值为 \(c\) 的边以及另一个点 \(r\),有 \(d_r\leftarrow\min(d_r,c+d_p+(r-q+d_p)\bmod n)\),直接松弛单次复杂度平方,显然过劣,稍微想想可以发现这东西等价于圆周上的距离,因此顺时针扫两遍即可线性松弛。注意由于这题边数可以达到 \(n^2\),是个稠密图,因此加上堆优化复杂度反而多一个 \(\log\),应用不带堆优化的 dijkstra,总复杂度 \(n^3\)。
const int MAXN=600;
int n,m,c[MAXN+5][MAXN+5];
bool vis[MAXN+5];ll d[MAXN+5][MAXN+5];
int get(int x){return (x>=n)?(x-n):x;}
int main(){
scanf("%d%d",&n,&m);
memset(c,63,sizeof(c));memset(d,63,sizeof(d));
for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),chkmin(c[u][v],w);
for(int i=0;i<n;i++){
d[i][i]=0;memset(vis,0,sizeof(vis));
for(int j=0;j<n;j++){
int p=n;
for(int k=0;k<n;k++) if(!vis[k]&&d[i][k]<d[i][p]) p=k;
vis[p]=1;int mn=INF,rem=d[i][p]%n;
for(int k=0;k<n*2;k++){
++mn;
chkmin(mn,c[p][get(get(k)-rem+n)]);
chkmin(d[i][get(k)],d[i][p]+mn);
}
}
for(int j=0;j<n;j++) printf("%lld%c",d[i][j]," \n"[j==n-1]);
}
return 0;
}
Codeforces 1528E Mashtali and Hagh Trees(*2900)
首先手玩几组数据可以发现一棵有向树满足第三个条件(也就是那个朋友的条件)当且仅当它是一棵外向树或是一棵内向树或是一个外向树与一个内向树用一条内向树根指向外向树根的边相连。考虑如何算之:
- 外向树与内向树求法是相同的,对于每棵外向树如果把其所有边都反向,即可形成一棵内向树,因此外向树个数 \(=\) 内向树个数,而一条有向链即是外向树也是内向树,因此第一、二类树的个数总和就是符合要求的外向树个数的两倍减 \(1\)。我们设 \(dp_i\) 表示有多少棵不同构的树满足最大深度为 \(i\) 且每个点儿子个数 \(\le 2\)(根节点深度为 \(0\)),转移就分根有一个儿子和根有两个儿子即可,即 \(dp_i=dp_{i-1}+\sum\limits_{j=0}^{i-2}dp_jdp_{i-1}+\dfrac{dp_{i-1}(dp_{i-1}+1)}{2}\)。那么由于根节点儿子个数可以达到 \(3\),因此最大深度为 \(n\) 的内向树个数需进行一些分类讨论:
- 如果根节点儿子个数 \(\le 1\),方案数就是 \(dp_n\)。
- 如果根节点儿子个数 \(=2\),那么设根节点三个儿子最大深度分别为 \(a,b,c(a\le b\le c)\),那么必须有 \(c=n\)。但是由于 \(\le\) 这个条件的存在,有可能会算重,需要分 \(a<b<c,a=b<c,a<b=c,a=b=c\) 四种情况再进行分类讨论。
- 对于第三种情况,我们定义一个点的深度为入度为 \(0\) 的点到其的最长距离,那么我们考虑枚举第一个入度 \(\ge 2\) 的点的深度 \(x\),那么根向树的方案数为 \(dp_x-dp_{x-1}\),减掉的那个 \(dp_{x-1}\) 是该节点入度为 \(1\) 的情况,叶向树的方案数为 \(dp_{n-1-x}-1\),减掉的那个 \(1\) 是一条链的情况,这时候的贡献已经在叶向树中计算过了。
时间复杂度线性。
const int INV2=MOD+1>>1;
const int INV6=(MOD+1)/6;
const int MAXN=1e6;
int n,dp[MAXN+5];
int main(){
scanf("%d",&n);dp[0]=1;
for(int i=1,sum=0;i<=n;i++){
dp[i]=1ll*dp[i-1]*sum%MOD;
dp[i]=(dp[i]+1ll*dp[i-1]*(dp[i-1]+1)%MOD*INV2)%MOD;
dp[i]=(dp[i]+dp[i-1])%MOD;sum=(sum+dp[i-1])%MOD;
// printf("%d %d\n",i,dp[i]);
} int ss=0;
for(int i=0,s=0;i<n-1;i++){
ss=(ss+1ll*s*dp[i])%MOD;
s=(s+dp[i])%MOD;
} int res=1ll*ss*dp[n-1]%MOD;
for(int i=0;i<n-1;i++) res=(res+1ll*dp[n-1]*dp[i]%MOD*(dp[i]+1)%MOD*INV2)%MOD;
for(int i=0;i<n-1;i++) res=(res+1ll*dp[n-1]*(dp[n-1]+1)%MOD*INV2%MOD*dp[i])%MOD;
res=(res+1ll*dp[n-1]*(dp[n-1]-1)%MOD*(dp[n-1]-2)%MOD*INV6)%MOD;
res=(res+1ll*dp[n-1]*(dp[n-1]-1))%MOD;res=(res+dp[n-1])%MOD;
res=(res+dp[n])%MOD;res=(res*2)%MOD;
for(int i=1;i<n-1;i++) res=(res+1ll*(dp[i]-1)*(dp[n-1-i]-dp[n-2-i]+MOD))%MOD;
printf("%d\n",(res-1+MOD)%MOD);
return 0;
}
Codeforces 1511F Chainword(*2700)
因为昨天做过了这场的 G,今天就顺便把 F 做了(
这道题就很套路了吧。。。
建出 \(n\) 个串的 trie,然后设 \(dp_{i,j}\) 表示目前第一段拼成的串的长度为 \(i\),第二段划分到 trie 树上第 \(j\) 个节点的方案数,然后发现转移只会从前面 \(5\) 个位置转移过来,于是开一个 \(5\times 5\times 8\) 的矩阵维护转移情况即可。由于字符串长度很小,转移矩阵可以大力暴搜求出。然后矩阵快速幂即可。
时间复杂度 \(|S|^6n^3\log m\)。
const int MAXN=8;
const int MAXP=41;
const int MAXL=5;
const int MAXS=215;
int n,m,ch[MAXP+5][27],ncnt=1,tot,ed[MAXP+5];
string s[MAXN+2];
void insert(string str){
int cur=1;
for(int i=0;i<str.size();i++){
if(!ch[cur][str[i]-'a']) ch[cur][str[i]-'a']=++ncnt;
cur=ch[cur][str[i]-'a'];
} ed[cur]=1;
}
struct mat{
u64 a[MAXS+5][MAXS+5];
mat(){memset(a,0,sizeof(a));}
mat operator *(const mat &rhs){
mat res;
for(int i=1;i<=tot;i++) for(int k=1;k<=tot;k++) for(int j=1;j<=tot;j++){
res.a[i][j]+=a[i][k]*rhs.a[k][j];
if((k&15)==0) res.a[i][j]%=MOD;
}
for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++) res.a[i][j]%=MOD;
return res;
}
void print(){
for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++)
printf("%llu%c",a[i][j]," \n"[j==tot]);
}
} trs,res;
int a[MAXL+2][MAXP+5][MAXP+5];
void dfsclc(int p,int id,int len,int ori){
if(ed[p]&&len!=0) dfsclc(1,id,len,ori);
if(len==s[id].size()) return a[len][ori][p]++,void();
if(ch[p][s[id][len]-'a']) dfsclc(ch[p][s[id][len]-'a'],id,len+1,ori);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
static char orzczx[MAXL+4];
scanf("%s",orzczx+1);int len=strlen(orzczx+1);
for(int j=1;j<=len;j++) s[i].pb(orzczx[j]);
insert(s[i]);
} tot=ncnt*5;
for(int i=1;i<=ncnt;i++) for(int j=1;j<=n;j++) dfsclc(i,j,0,i);
for(int i=1;i<=4;i++) for(int j=1;j<=ncnt;j++) trs.a[j+i*ncnt][j+(i-1)*ncnt]=1;
for(int i=1;i<=5;i++) for(int j=1;j<=ncnt;j++) for(int k=1;k<=ncnt;k++)
trs.a[(5-i)*ncnt+j][4*ncnt+k]=a[i][j][k];
// trs.print();
for(int i=1;i<=tot;i++) res.a[i][i]=1;
for(;m;m>>=1,trs=trs*trs) if(m&1) res=res*trs;
printf("%d\n",res.a[4*ncnt+1][4*ncnt+1]);
return 0;
}
Codeforces 1495D BFS Trees(*2600)
先考虑怎样求出一个点 \(x\) 的 BFS Tree 的数量。考虑将点按 \(x\) 到其的最短距离分层,那么显然每个点在 BFS Tree 上的父亲必须是某个上一层的节点,而如果我们钦定每个点都连向一个上一层的节点,那显然会形成一个树形结构,因此方案数就是对于与每个不同于源点的点 \(x\),与其相连的在其上一层的节点个数之积。
接下来考虑求解原问题。注意到对于两个点 \(i,j\),如果存在两个不同的点 \(x,y\),满足 \(dis_{i,x}+dis_{x,j}=dis_{i,j},dis_{i,y}+dis_{y,j}=dis_{i,j}\),且 \(dis_{i,x}=dis_{i,y}\) 那么答案肯定是 \(0\),因为 \(i\to x,x\to j,i\to y,y\to j\) 最短路上的节点肯定都要在 BFS Tree 上,这样构不成树形结构,否则对于在 \(i,j\) 最短路径上的点,它们之间的连边情况肯定是唯一的,我们就把它们缩起来然后跑前面的子问题即可。
实现时不必真的缩,只用统计对于某个不在 \(i,j\) 最短路上的点 \(x\),有多少个与之相连的点 \(y\) 满足 \(dis_{i,x}=dis_{i,y}+1,dis_{j,x}=dis_{j,y}+1\)。
时间复杂度 \(n^2m\)。
const int MAXN=400;
const int MAXM=600;
int n,m,dis[MAXN+5][MAXN+5];
link_list<int,MAXN,MAXM*2> g;
bool vis[MAXN+5];
int main(){
scanf("%d%d",&n,&m);memset(dis,63,sizeof(dis));
for(int i=1;i<=n;i++) dis[i][i]=0;
for(int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);dis[u][v]=dis[v][u]=1;
g.ins(u,v);g.ins(v,u);
}
for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
chkmin(dis[i][j],dis[i][k]+dis[k][j]);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
memset(vis,0,sizeof(vis));bool ban=0;
for(int k=1;k<=n;k++) if(dis[i][j]==dis[i][k]+dis[k][j]){
if(vis[dis[i][k]]){ban=1;break;}
vis[dis[i][k]]=1;
} int res=1;
if(!ban){
for(int k=1;k<=n;k++) if(dis[i][j]!=dis[i][k]+dis[k][j]){
int cnt=0;
for(int e=g.hd[k];e;e=g.nxt[e]){
int f=g.val[e];
if(dis[i][f]+1==dis[i][k]&&dis[j][f]+1==dis[j][k]) cnt++;
} res=1ll*res*cnt%MOD;
}
} printf("%d%c",(ban)?0:res," \n"[j==n]);
}
return 0;
}