#25 CF1172E & CF1299D & CF1097E
Nauuo and ODT
题目描述
解法
直接贡献法,对每种颜色分别统计贡献。假设计算到颜色 \(c\) 的时候,我们把是颜色 \(c\) 的点染白,把不是颜色 \(c\) 的点染黑。考虑正难则反,不包含这种颜色的路径数就是 \(\sum\) 黑色连通块大小的平方。
维护这东西其实是经典问题,我们把每个点的颜色放在其父边上,黑色代表连接,白色代表断开。这样转化之后,我们把图上的每个连通块去掉根节点,那么剩下的连通块就是黑色连通块了。
直接上 \(\tt lct\),维护虚子树的大小和虚子树大小的平方,把每个联通块的根贡献进答案(\(access\) 之后虚子树的平方和)
修改考虑离线处理,就可以转化成 \(n+2m\) 个关于某个点在某时刻变色的信息,可以差分贡献回去。
时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int M = 400005;
#define ll long long
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[M],b[M],fa[M],ch[M][2],p[M];
ll s[M],v1[M],v2[M],c[M],ans;
struct node{int x,y;};
vector<int> g[M];vector<node> v[M];
void dfs(int u)
{
for(int v:g[u]) if(v^p[u])
p[v]=u,dfs(v);
}
void up(int x)
{
s[x]=s[ch[x][0]]+s[ch[x][1]]+v1[x]+1;
}
int nrt(int x)
{
return x==ch[fa[x]][0] || x==ch[fa[x]][1];
}
int chk(int x)
{
return x==ch[fa[x]][1];
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;fa[w]=y;
if(nrt(y)) ch[z][chk(y)]=x;fa[x]=z;
ch[x][k^1]=y;fa[y]=x;
up(y);up(x);
}
void splay(int x)
{
while(nrt(x))
{
int y=fa[x];
if(nrt(y))
{
if(chk(x)==chk(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
v1[x]+=s[ch[x][1]];
v2[x]+=s[ch[x][1]]*s[ch[x][1]];
ch[x][1]=y;
v1[x]-=s[ch[x][1]];
v2[x]-=s[ch[x][1]]*s[ch[x][1]];
up(x);
}
}
void make(int u)
{
access(u);splay(u);
}
int findrt(int u)
{
make(u);
while(ch[u][0]) u=ch[u][0];
splay(u);return u;
}
void add(int u,int f)
{
int rt=findrt(u);make(rt);
ans+=f*v2[rt];
}
void link(int u,int v)
{
add(u,-1);add(v,-1);make(u);make(v);
fa[u]=v;v1[v]+=s[u];v2[v]+=s[u]*s[u];
add(v,1);
}
void cut(int u,int v)
{
add(u,-1);make(u);
fa[ch[u][0]]=0;ch[u][0]=0;
up(u);add(u,1);add(v,1);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
v[a[i]=read()].pb({i,0});
for(int i=1;i<n;i++)
{
int u=read(),v=read();
g[u].pb(v);g[v].pb(u);
}
dfs(1);p[1]=n+1;
for(int i=1;i<=n+1;i++) s[i]=1;
for(int i=1;i<=n;i++) link(i,p[i]);
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
v[a[x]].pb({x,i});
v[a[x]=y].pb({x,i});
}
for(int i=1;i<=n;i++)
{
ll last=0;int x=0;
for(auto t:v[i])
{
b[x=t.x]?link(x,p[x]):cut(x,p[x]);
c[t.y]+=1ll*n*n-ans-last;
last=1ll*n*n-ans;b[x]^=1;
}
for(auto t:v[i]) if(b[x=t.x])
link(x,p[x]),b[x]=0;
}
for(int i=1;i<=m;i++)
c[i]+=c[i-1];
for(int i=0;i<=m;i++)
printf("%lld\n",c[i]);
}
Around the World
题目描述
解法
关键的 \(\tt observation\) 是:对路径权值有影响的只有环的异或和,那么存在合法路径权值为 \(0\) 等价于存在选取若干个环的方案,使得这些环可以到达并且环的异或和为 \(0\)
那么有一个显然的思路:我们把可以到达环的权值的线性基作为状态 \(dp\),分别扫描每个连通块,转移就讨论合并上这个连通块的线性基,或者不合并。显然本题的难点是代码实现,下面按照时间顺序梳理思路。
首先我们处理出所有可能的线性基,可以直接搜索所有的最简线性基,发现只有 \(k=374\) 种。我们存下这些线性基,并且把它们哈希方便 \(dp\) 的时候获取编号。
处理每个连通块的时候,我们需要把连通块里面所有环插入线性基中。考虑这个连通块的 \(\tt dfs\) 树,发现我们只需要插入返祖边构成的环即可,注意插入线性基时还需要维护其最简性,此外还需记录是否可以异或和为 \(0\),也就是新插入的数可以被线性基里面已有的元素表示出来。
利用题目条件 不存在一个长度 >3 的简单环经过了 1 号点
,说明 \(1\) 最多向这个连通块连两条边。对于两条边的情况,我们还需要考虑只断一条边的转移,断一条边仅仅只会减少和根相连的这个环。所以在 \(\tt dfs\) 的时候还需要处理另一种线性基,这种线性基不插入和根相连的环。
时间复杂度 \(O(nk)\)
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,zxy,Ind,id[M],dfn[M],d[M],dp[M],f[M];
struct edge{int v,c;};vector<edge> g[M];
struct node
{
int a[5],fl;
node() {fl=0;memset(a,0,sizeof a);}
void clear() {fl=0;memset(a,0,sizeof a);}
int H()
{
return a[0]+a[1]*2+a[2]*8+
a[3]*64+a[4]*1024;
}
void ins(int x)
{
for(int i=4;i>=0;i--) if(x>>i&1)
{
if(!a[i])
{
for(int j=i-1;j>=0;j--)
if(x>>j&1) x^=a[j];
for(int j=4;j>i;j--)
if(a[j]>>i&1) a[j]^=x;
a[i]=x;return;
}
else x^=a[i];
}
fl=1;
}
}lb[400],A,B;
void add(int &x,int y) {x=(x+y)%MOD;}
void get(int x,int s)
{
if(x==-1)
{
lb[k]=A;id[A.H()]=k;k++;
return ;
}
A.a[x]=0;get(x-1,s);
if(!(s>>x&1)) for(int i=0;i<(1<<x);i++)
A.a[x]=(1<<x)|i,get(x-1,s|i);
}
int merge(node a,node b)
{
node r=a;
for(int i=4;i>=0;i--)
if(b.a[i]) r.ins(b.a[i]);
return r.fl?k:id[r.H()];
}
void dfs(int u,int fa)
{
dfn[u]=++Ind;
for(auto x:g[u])
{
int v=x.v,c=x.c;
if(v==fa) continue;
if(!dfn[v]) d[v]=d[u]^c,dfs(v,u);
else if(dfn[u]>dfn[v])
{
A.ins(d[u]^d[v]^c);
if(v!=1) B.ins(d[u]^d[v]^c);
else zxy=1;
}
}
}
int main()
{
n=read();m=read();get(4,0);
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read();
g[u].push_back({v,c});
g[v].push_back({u,c});
}
dp[0]=dfn[1]=Ind=1;
for(auto x:g[1])
{
int v=x.v,c=x.c;
if(dfn[v]) continue;
zxy=0;A.clear();B.clear();
d[v]=c;dfs(v,1);
for(int i=0;i<k;i++) f[i]=dp[i];
//do not delete
if(!A.fl) for(int i=0;i<k;i++)
if(f[i]) add(dp[merge(lb[i],A)],f[i]);
//delete one of them (if zxy is true)
if(zxy && !B.fl) for(int i=0;i<k;i++)
if(f[i]) add(dp[merge(lb[i],B)],2*f[i]%MOD);
}
int ans=0;
for(int i=0;i<k;i++) add(ans,dp[i]);
printf("%d\n",ans);
}
Egor and an RPG game
题目描述
解法
其实我们并不需要知道 \(f(n)\) 是什么,只需要给他定一个较紧的下界。考虑构造这样一种分层下降的排列,第 \(i\) 层有 \(i\) 个元素,即 \(\{1,3,2,6,5,4,10,9,8,7....\}\),那么如果有 \(k\) 个满层,则需要至少 \(k\) 个划分:
那么我们尝试构造一种只需要 \(c(n)\) 个的划分,可以归纳地构造,首先求出当前序列的 \(\tt LIS\),设长度为 \(m\)
若 \(m>c(n)\),可以直接把这个 \(\tt LIS\) 划分出来,已知长度为 \(n-m\) 只需要 \(c(n-m)\) 步就可以构造出来。而我们又知道 \(c(n-m)\leq c(n)-1\),自然归纳到了更小的情况。
若 \(m\leq c(n)\),根据 \(\tt dilworth\) 定理,原序列一定可以划分成 \(m\) 个下降子序列(并且不能再少了),这种情况就是归纳的出口。具体构造可以考虑求 \(\tt LIS\) 的经典方法,直接把它放在 \(\tt lower\_bound\) 到的位置的下降子序列中即可。
由于递归次数是 \(O(\sqrt n)\) 的,并且每一次递归都需要暴力求 \(\tt LIS\),时间复杂度 \(O(n\sqrt n\log n)\)
总结
对于这种 构造次数不超过所有情况中最劣的最小次数
的题目,可以先确定一个较紧的下界,然后归纳地构造。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
#define vi vector<int>
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,b[M],q[M],p[M],v[M];vi ans[M];
int get(int n)
{
int r=0;
while(r*(r+1)/2<=n) r++;
return r-1;
}
void work(vi a)
{
int n=a.size()-1,k=0;
if(n<=0) return ;
for(int i=1;i<=n;i++)
{
int x=lower_bound(q+1,q+1+k,i,
[&](int i,int j){return a[i]<a[j];})-q;
p[i]=q[x-1];q[x]=i;b[i]=x;k+=(x==k+1);
}
if(k>get(n))
{
for(int i=0;i<=n;i++) b[i]=0;
for(int i=q[k];i;i=p[i]) b[i]=1;
m++;vi d;d.pb(0);
for(int i=1;i<=n;i++)
{
if(b[i]) ans[m].pb(a[i]);
else d.pb(a[i]);
}
work(d);return ;
}
for(int i=1;i<=n;i++)
ans[m+b[i]].pb(a[i]);
m+=k;
}
int main()
{
T=read();
while(T--)
{
n=read();m=0;vi a;a.pb(0);
for(int i=1;i<=n;i++) a.pb(read());
work(a);
printf("%d\n",m);
for(int i=1;i<=m;i++)
{
printf("%d",ans[i].size());
for(int x:ans[i]) printf(" %d",x);
ans[i].clear();puts("");
}
}
}