这几天都在打音游,没搞学啊。
啊啊,懒得新开了,就在这里写吧。
063 P6383 『MdOI R2』Resurrection
容易发现 \(n\) 号结点是很特殊的,我们不妨将其设为原树和新树的根。
可以发现一个结点在新树的父亲仍然是其在原树的祖先。(其父亲连通块权值大于等于其父亲的点)
给出结论:一棵新树合法当且仅当一个点在新树的父亲是其所有子孙在新树的父亲的子孙,即不存在 \(u,v\)(令其新树父亲分别为 \(f_u,f_v\)),满足原树上 \(fa_u\rightarrow fa_v\rightarrow u\rightarrow v\)。(靠左的为祖先)
必要性:
断开原树上 \(u\) 和父亲的边前,\(fa_u\rightarrow u\) 不能有任何边断开,而断开后就无法完成 \(fa_v\) 与 \(v\) 的连边。
充分性:
维护一个集合 \(S\),初始为 \(n\) 在新树上所有儿子,每次取编号最小的点删除与原树父亲的连边,并将其新树上儿子加入 \(S\)。
那么删一个点时其新树父亲的父边已经断开了,而中间每一条边一定没被断开。
得到这个结论,令 \(f_{i,j}\) 为 \(i\) 的祖先中还有 \(j\) 个可以选的方案数,转移用前缀和优化即可。
复杂度 \(O(n^2)\)。
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=3005,mod=998244353;
int n,ans;
int f[maxn][maxn],dep[maxn];
vector<int>v[maxn];
void dfs(int x,int last){
dep[x]=dep[last]+1;
for(int i=0;i<v[x].size();i++)
if(v[x][i]!=last)
dfs(v[x][i],x);
for(int t=1;t<=dep[x];t++){
int mul=1;
for(int i=0;i<v[x].size();i++)
if(v[x][i]!=last)
mul=1ll*mul*f[v[x][i]][t+1]%mod;
f[x][t]=(f[x][t-1]+mul)%mod;
}
}
int main(){
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
dep[0]=-1,dfs(n,0),ans=1;
for(int i=0;i<v[n].size();i++)
ans=1ll*ans*f[v[n][i]][1]%mod;
printf("%d\n",ans);
return 0;
}
064 P8427 [COI2020] Paint
md,调了好久。。。
一眼根号分治,对颜色集合大小分治:
小集合暴力遍历所有邻居暴力合并,大集合用哈希表保存各个颜色的邻居对应 vector,小集合保存每个大集合邻居,然后用并查集+启发式合并维护一下集合之间的合并就好了。
令 \(T=nm\),复杂度 \(O(T\sqrt T+T\log T)\)。
#include<stdio.h>
#include<vector>
#include<unordered_map>
using namespace std;
const int maxn=200005,S=400;
int n,m,T,tot,stp;
int px[maxn],py[maxn],bel[maxn],col[maxn],vis[maxn],dsu[maxn],big[maxn];
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,-1,1};
vector<int>a[maxn],pos[maxn],nbig[maxn];
unordered_map< int,vector<int> >fnd[maxn];
inline int getpos(int x,int y){
return (x-1)*m+y;
}
int find(int x){
return dsu[x]==x? x:dsu[x]=find(dsu[x]);
}
void dfs(int x,int y,int c){
bel[getpos(x,y)]=c,vis[getpos(x,y)]=stp,pos[c].push_back(getpos(x,y));
for(int i=1;i<=4;i++){
int ux=x+dx[i],uy=y+dy[i];
if(ux>=1&&ux<=n&&uy>=1&&uy<=m&&vis[getpos(ux,uy)]!=stp&&a[ux][uy]==col[c])
dfs(ux,uy,c);
}
}
void trans(int id){
big[id]=1,stp++;
for(int t=0;t<pos[id].size();t++){
int p=pos[id][t],x=px[p],y=py[p];
for(int i=1;i<=4;i++){
int ux=x+dx[i],uy=y+dy[i];
if(ux>=1&&ux<=n&&uy>=1&&uy<=m){
int up=bel[getpos(ux,uy)];
if(vis[up]!=stp&&up!=id)
vis[up]=stp,fnd[id][col[up]].push_back(up),nbig[up].push_back(id);
}
}
}
}
int merge(int a,int b){
if(pos[a].size()>pos[b].size())
swap(a,b);
dsu[a]=b;
if(big[a]==0&&big[b])
trans(a);
while(!pos[a].empty())
bel[pos[a].back()]=b,pos[b].push_back(pos[a].back()),pos[a].pop_back();
for(unordered_map< int,vector<int> >::iterator it=fnd[a].begin();it!=fnd[a].end();it++){
for(int i=0;i<(it->second).size();i++)
fnd[b][it->first].push_back((it->second)[i]);
(it->second).clear();
}
fnd[a].clear();
vector<int>V;
stp++;
while(!nbig[a].empty()){
int x=find(nbig[a].back());
if(vis[x]!=stp)
vis[x]=stp,V.push_back(x);
nbig[a].pop_back();
}
while(!nbig[b].empty()){
int x=find(nbig[b].back());
if(vis[x]!=stp)
vis[x]=stp,V.push_back(x);
nbig[b].pop_back();
}
swap(V,nbig[b]);
if(big[b]==0&&pos[b].size()>S)
trans(b);
return b;
}
int main(){
// freopen("tmp.in","r",stdin);
// freopen("tmp.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
a[i].resize(m+1);
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]),px[getpos(i,j)]=i,py[getpos(i,j)]=j;
}
stp++;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(vis[getpos(i,j)]==0)
tot++,dsu[tot]=tot,col[tot]=a[i][j],dfs(i,j,tot);
for(int i=1;i<=tot;i++)
if(pos[i].size()>S)
trans(i);
scanf("%d",&T);
while(T--){
int x,y,c,b;
scanf("%d%d%d",&x,&y,&c),b=bel[getpos(x,y)];
vector<int>V;
col[b]=c,stp++;
if(big[b]==0)
for(int t=0;t<pos[b].size();t++){
int p=pos[b][t],x=px[p],y=py[p];
for(int i=1;i<=4;i++){
int ux=x+dx[i],uy=y+dy[i];
if(ux>=1&&ux<=n&&uy>=1&&uy<=m){
int up=find(bel[getpos(ux,uy)]);
if(vis[up]!=stp&&col[up]==c&&up!=b)
vis[up]=stp,V.push_back(up);
}
}
}
else while(!fnd[b][c].empty()){
int x=find(fnd[b][c].back());
fnd[b][c].pop_back();
if(vis[x]!=stp&&col[x]==c&&x!=b)
vis[x]=stp,V.push_back(x);
}
for(int i=0;i<V.size();i++)
b=merge(b,V[i]);
stp++;
for(int i=0;i<nbig[b].size();i++){
int x=find(nbig[b][i]);
if(vis[x]!=stp)
vis[x]=stp,fnd[x][c].push_back(b);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
printf("%d%c",col[bel[getpos(i,j)]],j==m? '\n':' ');
return 0;
}
065 CF1713F Lost Array
场上想了很久,终究没做出来。。。
需要积累经典问题的多种解法。
转化一下题意,翻转 \(a\) 的下标,\(b_i\) 相当于所有 \(i\operatorname{and}j=0\) 的 \(a_j\) 对应异或和。
这种问题我们常常会对 \(b\) 取反,然后做 FWT,但是在此题中我们并不知道 \(b_i\) 后若干项,所以这个做法行不通。(在扩充长度之后)
另一个思路是我们先考虑 \(a\) 变化成 \(b\)。进行容斥,我们枚举重合的子集 \(S\) 进行容斥,我们只需对 \(S\) 超集的 \(a\) 之和乘容斥系数求和即可,而由于是异或,容斥系数可以忽略。
也就是说 \(a\) 变化成 \(b\) 可以通过求超集和再求子集和变化,由于是异或,逆操作只需先求子集和(注意要截断)再求超集和。
复杂度 \(O(n\log n)\)。
#include<stdio.h>
int n,m;
int a[1<<19];
int main(){
scanf("%d",&n),m=1;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
while(m<n)
m<<=1;
for(int len=2;len<=m;len<<=1)
for(int i=0,now=len>>1;i<m;i+=len)
for(int j=0;j<now&&i+j+now<n;j++)
a[i+j+now]^=a[i+j];
for(int len=2;len<=m;len<<=1)
for(int i=0,now=len>>1;i<m;i+=len)
for(int j=0;j<now;j++)
a[i+j]^=a[i+j+now];
for(int i=n-1;i>=0;i--)
printf("%d%c",a[i],i==0? '\n':' ');
return 0;
}
066 AT5042 Edge Ordering
编号相当于一个加边顺序,使得得到的第一棵生成树是要求的生成树。
那么我们就可以得知一条非树边需要在哪些边之后加入,我们状压来维护转移顺序,那么限制就变成了限定一些非树边在某个时刻之后加入。
这个限制并不是很好刻画,考虑逆序加边过程,相当于每次插入一条树边在最前面,或者将一条非树边加入在任意位置。
然后算一算转移就好了,令当前加入了 \(a\) 条边,\(b\) 条是树边,方案数为 \(f\),方案权值和为 \(g\),那么:
- 加入一条树边:\(f\rightarrow f,g\rightarrow g+(b+1)f\);
- 加入一条非树边:\(f\rightarrow (a+1)f,g\rightarrow (a+2)g\)。
复杂度 \(O(2^nn)\)。
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=25,maxs=1<<19,mod=1000000007;
int n,m;
int dep[maxn],fa[maxn],rec[maxn],f[maxs],g[maxs],t[maxs],cnt[maxs],fac[505],nfac[505];
vector<int>v[maxn],w[maxn];
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)
res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
void dfs(int x,int last){
dep[x]=dep[last]+1;
for(int i=0;i<v[x].size();i++)
if(v[x][i]!=last)
fa[v[x][i]]=x,rec[v[x][i]]=1<<(w[x][i]-1),dfs(v[x][i],x);
}
int get(int x,int y,int v){
if(x==y)
return v;
return dep[x]<dep[y]? get(x,fa[y],v^rec[y]):get(fa[x],y,v^rec[x]);
}
inline int calc(int a,int b){
return 1ll*fac[b]*nfac[a-1]%mod;
}
int main(){
fac[0]=nfac[0]=1;
for(int i=1;i<=500;i++)
fac[i]=1ll*fac[i-1]*i%mod,nfac[i]=ksm(fac[i],mod-2);
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x),w[x].push_back(i),w[y].push_back(i);
dfs(1,0);
for(int i=1,x,y;i<=m-n+1;i++)
scanf("%d%d",&x,&y),t[((1<<(n-1))-1)^get(x,y,0)]++;
for(int len=2;len<=(1<<(n-1));len<<=1)
for(int i=0,now=len/2;i<(1<<(n-1));i+=len)
for(int j=0;j<now;j++)
t[i+j]+=t[i+j+now];
f[0]=1,g[0]=0;
for(int i=1;i<(1<<(n-1));i++){
cnt[i]=__builtin_popcount(i)+m-n+1-t[i];
for(int j=1;j<n;j++)
if((i>>(j-1))&1){
int k=i^(1<<(j-1));
f[i]=(f[i]+1ll*f[k]*calc(cnt[k]+1,cnt[k]+(t[k]-t[i])))%mod;
g[i]=(g[i]+1ll*g[k]*calc(cnt[k]+2,cnt[k]+(t[k]-t[i])+1))%mod;
}
g[i]=(g[i]+1ll*f[i]*__builtin_popcount(i))%mod;
}
printf("%d\n",g[(1<<(n-1))-1]);
return 0;
}
067 CF1712F Triameter
考场想点分治去了。。。
求出每个点到叶子的最短距离 \(f_i\),问题变为计算:(其中 \(C\) 为询问给定值)
答案具有可二分性,但是这个 \(\log\) 并不必要。我们可以在枚举 \(f_x\) 的过程中检查答案是否可以增加,那么问题变为:
对于所有 \(f_y\geqslant ans-f_x-C\),求出所有 \(f_x\) 为某个值与这些点的距离最大值。
经典的树上点集直径问题,维护点集直径端点即可,复杂度 \(O(Tn+n\log n)\)。
#include<stdio.h>
#include<vector>
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
using namespace std;
const int maxn=1000005,maxk=21;
int n,m,rt,ans,dfns,mx;
int f[maxn],g[maxn],in[maxn],dep[maxn],st[maxn<<1][maxk],lg[maxn<<1],res[maxn][2],suf[maxn][2];
vector<int>v[maxn];
void read(int &x){
x=0;
char c=getchar();
for(;c<'0'||c>'9';c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=x*10+c-48;
}
void dfs1(int x,int last){
dep[x]=dep[last]+1,in[x]=++dfns,st[dfns][0]=x;
if(v[x].size()==1){
f[x]=0;
return ;
}
f[x]=1e9;
for(int i=0;i<v[x].size();i++){
int y=v[x][i];
if(y!=last)
dfs1(y,x),f[x]=min(f[x],f[y]+1),st[++dfns][0]=x;
}
}
void dfs2(int x,int last){
if(last==0)
g[x]=f[x];
else g[x]=min(g[last],f[last])+1;
for(int i=0;i<v[x].size();i++)
if(v[x][i]!=last)
dfs2(v[x][i],x);
}
inline int calc(int a,int b){
return dep[a]<dep[b]? a:b;
}
inline int lca(int a,int b){
if(in[a]>in[b])
swap(a,b);
int l=in[a],r=in[b],k=lg[r-l+1];
return calc(st[l][k],st[r-(1<<k)+1][k]);
}
inline int get(int a,int b){
return dep[a]+dep[b]-2*dep[lca(a,b)];
}
void merge(int *a,int *b){
if(a[0]==0){
a[0]=b[0],a[1]=b[1];
return ;
}
if(b[0]==0)
return ;
int res=get(a[0],a[1]),c[2]={a[0],a[1]};
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++){
int v=get(a[i],b[j]);
if(v>res)
res=v,c[0]=a[i],c[1]=b[j];
}
int v=get(b[0],b[1]);
if(v>res)
res=v,c[0]=b[0],c[1]=b[1];
a[0]=c[0],a[1]=c[1];
}
int check(int d,int x,int lim){
int d0=max(0,lim-d-x);
if(d0>mx)
return 0;
int mx=0;
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
mx=max(mx,get(res[d][i],suf[d0][j]));
return mx>=lim;
}
int main(){
read(n);
for(int i=2,x;i<=n;i++)
read(x),v[x].push_back(i),v[i].push_back(x);
for(int i=1;i<=n;i++)
if(v[i].size()>1)
rt=i;
dfs1(rt,0),dfs2(rt,0);
lg[0]=-1;
for(int i=1;i<=dfns;i++)
lg[i]=lg[i>>1]+1;
for(int i=1;i<=20;i++)
for(int j=1;j+(1<<i)-1<=dfns;j++)
st[j][i]=calc(st[j][i-1],st[j+(1<<(i-1))][i-1]);
for(int i=1;i<=n;i++){
int c[2]={i,i},k=min(f[i],g[i]);
merge(res[k],c),mx=max(mx,k);
}
suf[mx][0]=res[mx][0],suf[mx][1]=res[mx][1];
for(int i=mx-1;i>=0;i--)
suf[i][0]=res[i][0],suf[i][1]=res[i][1],merge(suf[i],suf[i+1]);
read(m);
for(int t=1,x;t<=m;t++){
read(x),ans=0;
for(int i=0;i<=mx;i++)
while(check(i,x,ans+1))
ans++;
printf("%d%c",ans,t==m? '\n':' ');
}
return 0;
}
068 AGC041D Problem Scores
可以发现,第三条限制中,\(k=\lceil\frac n2\rceil\) 对应的限制一定是最强的,我们只需考虑它。
递增限制可以用“初始所有值为 \(n\),后面进行若干次前缀减”操作刻画,而第三条限制我们只需维护差值,这是一个 \(\leqslant n\) 的正整数,于是背包即可。
注意,此时不能使用后缀加来刻画操作,原因是其对差值的影响是可正可负的,不好处理。
#include<stdio.h>
const int maxn=5005;
int n,mod,ans;
int f[maxn];
int main(){
scanf("%d%d",&n,&mod);
f[0]=1;
for(int i=1;i<=n;i++){
int v=i<=(n+1)/2? i:(n-i+1);
for(int j=v;j<n;j++)
f[j]=(f[j]+f[j-v])%mod;
}
for(int i=0;i<n;i++)
ans=(ans+f[i])%mod;
printf("%d\n",ans);
return 0;
}
069 AGC058F Authentic Tree DP
很厉害的一道题目啊!!!
这道题巧妙利用了取模下的性质:\(x\) 的逆元等于 \(x+P\) 的逆元。(\(P=998244353\))
看到这个转移方程,肯定要赋予其一个组合意义:
将每条边建出一个对应点(称其为“新点”)连接其原来连接的两个点,然后在新点下面加入 \(P-1\) 个叶子。
然后给每个点赋予一个随机实数权值,求所有新点权值大于其邻居的概率。
那么转移方程就对应着枚举最大值,除以的 \(n\) 其实对应 \((n-1)P+n\)。
这个问题可以通过容斥解决,我们任取一个结点作为根,保留新点权值大于其儿子的限制,对新点权值大于其父亲的限制容斥。可以发现树分成了若干个互不影响的子连通块。(其结构是一棵有向树)
根据经典拓扑序结论,概率是每个点子树大小的倒数之乘积。于是令 \(f_{i,j}\) 表示 \(i\) 下面挂的连通块大小为 \(j\) 的方案数做树上背包即可。
复杂度 \(O(n^2)\)。
#include<stdio.h>
#include<vector>
using namespace std;
const int maxn=5005,mod=998244353;
int n,ans;
int sz[maxn],f[maxn][maxn],inv[maxn],g[maxn];
vector<int>v[maxn];
void dfs(int x,int last){
sz[x]=1,f[x][1]=1;
for(int t=0;t<v[x].size();t++){
int y=v[x][t];
if(y==last)
continue;
dfs(y,x);
int sum=0;
for(int i=1;i<=sz[y];i++)
sum=(sum+f[y][i])%mod;
for(int i=1;i<=sz[x];i++)
for(int j=1;j<=sz[y];j++)
g[i+j]=(g[i+j]+1ll*f[x][i]*f[y][j])%mod;
sz[x]+=sz[y];
for(int i=0;i<=sz[x];i++)
f[x][i]=(1ll*f[x][i]*sum-g[i]+mod)%mod,g[i]=0;
}
for(int i=1;i<=sz[x];i++){
f[x][i]=1ll*f[x][i]*inv[i]%mod;
if(last!=0)
f[x][i]=1ll*f[x][i]*inv[i]%mod;
}
}
int main(){
scanf("%d",&n),inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
dfs(1,0);
for(int i=1;i<=n;i++)
ans=(ans+f[1][i])%mod;
printf("%d\n",ans);
return 0;
}
070 AGC027F Grafting
首先判掉两棵树本身相同,那么一定会有一次操作。
枚举第一次操作对应的两个点 \(a,b\),发现最优方案会操作的点有且仅有在两棵树父亲不同的点。
那么就只需要给这些点的操作确定一个顺序,限制只有对于一对要操作的原树父子,父亲在儿子后操作,对于一对要操作的新树父子,父亲在儿子前操作。还有,一个点要操作其原树父亲一定要操作。
拓扑排序判一下是否合法即可做到 \(O(Tn^3)\)。
实际上存在 \(O(Tn^2)\) 的算法,具体地我们讨论答案的取值是否为 \(n\):(下面的说法纯口胡,没有实践过)
若不为 \(n\),就存在一个点是不被操作的,枚举这个点作为根。可以做类似的判定。
若为 \(n\),只枚举第一次操作的叶子 \(a\) 作为根,将原树看成无根树,且每个点都要进行操作,那么如果能拓扑出 \(n-1\) 个点,没有被拓扑到的就会是 \(a\) 接到的 \(b\)。
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
const int maxn=55;
int T,n,flg,ans;
int fa[maxn][2],deg[maxn];
vector<int>v[maxn],w[maxn],g[maxn];
queue<int>q;
inline void add(int x,int y){
g[x].push_back(y),deg[y]++;
}
void dfs(int x,int last,int typ){
fa[x][typ]=last;
for(int i=0;i<(typ==0? v:w)[x].size();i++){
int y=(typ==0? v:w)[x][i];
if(y!=last)
dfs(y,x,typ);
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
for(int i=1,x,y;i<n;i++)
scanf("%d%d",&x,&y),w[x].push_back(y),w[y].push_back(x);
ans=n+1;
for(int a=1;a<=n;a++)
if(v[a].size()==1){
dfs(a,0,0),dfs(a,0,1),flg=1;
for(int i=1;i<=n;i++)
flg&=(fa[i][0]==fa[i][1]);
if(flg){
ans=0;
break;
}
for(int b=1;b<=n;b++)
if(a!=b){
dfs(b,0,0),fa[b][0]=a,fa[a][0]=0;
flg=0;
for(int i=1;i<=n;i++)
if(fa[i][0]==fa[i][1]&&fa[fa[i][0]][0]!=fa[fa[i][0]][1])
flg=1;
if(flg)
continue;
int cnt=0,tot=0;
for(int i=1;i<=n;i++){
int u=fa[i][0],v=fa[i][1];
if(u!=v){
tot++;
if(fa[u][0]!=fa[u][1])
add(i,u);
if(fa[v][0]!=fa[v][1])
add(v,i);
}
}
for(int i=1;i<=n;i++)
if(fa[i][0]!=fa[i][1]&°[i]==0)
q.push(i);
while(!q.empty()){
int x=q.front();
q.pop(),cnt++;
for(int i=0;i<g[x].size();i++){
int y=g[x][i];
deg[y]--;
if(deg[y]==0)
q.push(y);
}
}
if(cnt==tot)
ans=min(ans,cnt+1);
for(int i=1;i<=n;i++)
g[i].clear(),deg[i]=0;
}
}
printf("%d\n",ans<=n? ans:-1);
for(int i=1;i<=n;i++)
v[i].clear(),w[i].clear();
}
return 0;
}
071 CF798E Mike and code of a permutation
仍然是