atcoder 杂题 #05
- abc340_g Leaf Color
- abc340_f F - S = 1
- abc361_g Go Territory
- abc386_f Operate K
abc340_g
独立想出了这道题。
如果我们确定了子图的叶子,那么这个子图就确定了。又由于叶子的颜色要相同,所以每种颜色的贡献是互相独立的。
首先如果一种颜色有 \(x\) 个点,那么这种颜色的贡献并不是 \(2^x-1\),因为可能有一个合法的子图中,这种颜色的点不在叶子处,答案就会多算。
考虑枚举颜色,把这种颜色的点取出来,建出虚树。然后在虚树上 DP。接下来如果一个点的颜色为我们枚举的这个颜色,我们称这个点有颜色。
设 \(f_i\) 表示子树 \(i\) 内的合法子图数,这里的合法子图指包含 \(i\) 的合法子图或者是空集,如果是空集就要求 \(i\) 有颜色,这样才能不多算。那么就有
接下来,对于一个有颜色的点 \(i\),它可以作为合法子图的叶子,所以它的度数没有限制,答案直接加上 \(f_i\),再让 \(f_i\) 加一,表示可以为空集。
而对于一个没有颜色的点 \(i\),它不可以作为合法子图的叶子,即度数要大于一,于是答案在加 \(f_i\) 的基础上还要减去度数为一的子图和空集。即
里面的 \(f_j-1\) 是因为要减去 \(j\) 子树为空集的情况。
时间复杂度的瓶颈在于建虚树,时间复杂度 \(O(n\log n)\)。
AC 代码:
const int N=2e5+5;
vector<int> g[N],h[N];
int n,co[N];
vector<int> c[N];
int fa[N][18],dep[N],dfn[N],dfn1;
void dfs1(int x,int y){dep[x]=dep[y]+1,fa[x][0]=y,dfn[x]=++dfn1;fu(i,1,18)fa[x][i]=fa[fa[x][i-1]][i-1];for(int v:h[x])if(v!=y)dfs1(v,x);
}
int lca(int x,int y){if(dep[x]<dep[y])swap(x,y);for(int i=dep[x]-dep[y],j=0;i;i>>=1,++j)if(i&1)x=fa[x][j];if(x==y)return x;fd(i,17,0)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];return fa[x][0];
}
int d[N*2],tot,Co;
const int mod=998244353;
int f[N],ans;
void dfs2(int x,int y){f[x]=1;int sum=0;for(int v:g[x]){if(v==y)continue;dfs2(v,x);f[x]=(ll)f[x]*f[v]%mod;sum=(sum+f[v])%mod;sum=(sum-1)%mod;}if(co[x]==Co){ans=(ans+f[x])%mod;f[x]=(f[x]+1)%mod;}else{ans=(ans+f[x])%mod;ans=(ans+mod-sum-1)%mod;}
}
signed main(){cin>>n;fo(i,1,n)cin>>co[i],c[co[i]].push_back(i);fu(i,1,n){int u,v; cin>>u>>v;h[u].push_back(v),h[v].push_back(u);}dfs1(1,0);fo(i,1,n){if(!c[i].size())continue;d[tot=1]=1;for(auto j:c[i])d[++tot]=j;auto cmp=[](int x,int y)->bool{return dfn[x]<dfn[y];};sort(d+1,d+1+tot,cmp);for(int o=tot,i=2;i<=o;++i)d[++tot]=lca(d[i],d[i-1]);sort(d+1,d+1+tot,cmp);tot=unique(d+1,d+1+tot)-d-1;fo(i,1,tot)g[d[i]].clear();auto add=[](int x,int y)->void{g[x].push_back(y),g[y].push_back(x);return;};fo(i,2,tot)add(d[i],lca(d[i],d[i-1]));Co=i;dfs2(1,0);}cout<<ans<<'\n';return 0;
}
abc340_f
除夕夜的 abc 在 12 月才改出来。
假设 \((x,y),(a,b),(0,0)\) 组成了面积为 \(1\) 的三角形。用正方形减去三个三角形得
即
用 exgcd 解出 \(a,b\) 即可,根据 exgcd 的技巧,当 \(\gcd(y,x)\nmid2\) 时无解,记得最后要输出 \(a,-b\)。
AC 代码:
#define int ll
int exgcd(int a,int b,int &x,int &y){if(b==0){x=1,y=0;return a;}int d=exgcd(b,a%b,y,x);y-=a/b*x;return d;
}
signed main(){int a,b;cin>>a>>b;int x,y;int d=exgcd(a,b,y,x);if(2%d!=0){cout<<"-1";return 0;}x*=2/d,y*=2/d;cout<<-x<<" "<<y<<"\n";return 0;
}
abc361_g
还记得是上个学期 2019pzr 还在的时候他讲的这道题,当时他好像还讲复杂了,没有听懂。
其实并不难写。
首先有一个 \(O(V^2)\) 的暴力,对于平面上的每一个点用并查集合并。
优化。考虑对于每一个 \(x\),给定的点把 \(y\) 分成了很多个段,发现一共是 \(O(n+V)\) 个段,用并查集合并这些段,用双指针扫一遍合并相邻 \(x\) 的每个段即可。
AC 代码:
const int N=1e6+5;
int n;
struct arr{int l,r,num;
};
int tot;
int fa[N],sz[N];
int getfa(int x){if(fa[x]==x)return x;return fa[x]=getfa(fa[x]);
}
vector<arr> a[N];
vector<int> pt[N];
signed main(){cin>>n;fo(i,1,n){int x,y;cin>>x>>y;pt[x].push_back(y);}fo(i,0,2e5){sort(pt[i].begin(),pt[i].end());if(pt[i].size()==0){a[i].push_back({0,(int)2e5,++tot});fa[tot]=tot,sz[tot]=2e5+1;}else if(pt[i][0]>0){a[i].push_back({0,pt[i][0]-1,++tot});fa[tot]=tot,sz[tot]=pt[i][0]; }fu(j,0,pt[i].size()){if(j+1==pt[i].size())continue;int u=pt[i][j],v=pt[i][j+1];if(u+1<v){a[i].push_back({u+1,v-1,++tot});fa[tot]=tot,sz[tot]=v-u-1;}}if(pt[i].size()>0){a[i].push_back({pt[i][pt[i].size()-1]+1,(int)2e5,++tot});fa[tot]=tot,sz[tot]=2e5-pt[i][pt[i].size()-1];}}fo(i,0,2e5){if(a[i].front().l==0)fa[a[i].front().num]=1;if(a[i].back().r==(int)2e5)fa[a[i].back().num]=1;}for(auto i:a[0])fa[i.num]=1;for(auto i:a[(int)2e5])fa[i.num]=1;fu(i,0,2e5){int k=0;for(auto j:a[i]){while(k<a[i+1].size()&&a[i+1][k].r<j.l)++k;if(k<a[i+1].size()&&a[i+1][k].l<=j.r){int u=getfa(j.num),v=getfa(a[i+1][k].num);if(u!=v)fa[u]=v;}while(k+1<a[i+1].size()&&a[i+1][k+1].l<=j.r){++k;int u=getfa(j.num),v=getfa(a[i+1][k].num);if(u!=v)fa[u]=v;}}}ll ans=0;fo(i,1,tot)if(getfa(i)!=getfa(1))ans+=sz[i];cout<<ans<<'\n';return 0;
}
abc386_f Operate K
题目大意:给定串 \(A,B\),可以做至多 \(K(K\le 20)\) 次以下操作之一:
- 往 \(A\) 中插入一个字符。
- 删除 \(A\) 的一个字符。
- 替换 \(A\) 的一个字符。
问最终能否将 \(A\) 变成 \(B\)。
解题思路:首先暴力地想,设\(f_{i,j}\) 表示 \(A\) 的前 \(i\) 个考虑完后,匹配到 \(B\) 的第 \(j\) 个字符最少做多少次操作。
注意到只有 \(i-K\le j\le i+K\) 的 \(j\) 是有用的,所以我们设新的 DP 状态 \(f_{i,j}(-K\le j\le K)\) 表示 \(A\) 的前 \(i\) 个考虑完后,匹配到 \(B\) 的第 \(i+j\) 个字符最少做多少次操作。
那么转移就是:
- 插入 \(B\) 的下一个字符:\(f_{i,j}+1\to f_{i,j+1}\)。
- 删除 \(A\) 的下一个字符:\(f_{i,j}+1\to f_{i+1,j-1}\)。
- 替换 \(A\) 的下一个字符为 \(B\) 的下一个字符:\(f_{i,j}+1\to f_{i+1,j}\)。
- 若 \(A_{i+1}=B_{i+j+1}\) 则直接匹配:\(f_{i,j}\to f_{i+1,j}\)。
转移过程中要求操作数始终不大于 \(K\) 即可。
时间复杂度 \(O(nK)\),其中 \(n\) 为字符串的长度。
AC 代码:
int K;
const int N=5e5+5;
char a[N],b[N];
int n,m;
int f[N][45];
void get(int &x,int y){if(y<x)x=y;
}
signed main(){cin>>K;scanf("%s%s",a+1,b+1);n=strlen(a+1),m=strlen(b+1);memset(f,0x3f,sizeof f);f[0][0+K]=0;fo(i,0,n){fo(j,-K,K){if(f[i][j+K]>K||i+j<0||i+j>m)continue;if(j+K-1>=0)get(f[i+1][j+K-1],f[i][j+K]+1);get(f[i][j+K+1],f[i][j+K]+1);get(f[i+1][j+K],f[i][j+K]+1);if(a[i+1]==b[i+j+1])get(f[i+1][j+K],f[i][j+K]);}}if(m-n<=K&&m-n>=-K&&f[n][m-n+K]<=K)cout<<"Yes\n";else cout<<"No\n";return 0;
}