这次赛时没打,赛后补了下题目,被最后一题打爆,特意学了一波相关姿势。
Triple Sort
略
Chef and Bitwise Product
略
Sorting Vases
显然 M M M个交换的意义就是将位置划分为若干等价类。那么可以将最初的每个数 i i i写成 ( u i , v i ) (u_i,v_i) (ui,vi)的形式,代表一开始在等价类 u i u_i ui,最后要到等价类 v i v_i vi。
考虑将数字间的交换当做一条边,那么得到的每个弱连通分量需要满足每个等价类在 u u u中出现次数等于在 v v v中出现次数。并且对于一个大小为 k k k的弱连通分量,至少需要交换 k − 1 k-1 k−1次,那么假设 N N N个数最多能划分为 w w w个合法的可以作为弱连通分量集合的非空集合,答案下界即为 N − w N-w N−w,并且容易达到这个下界。
现在问题是求出 w w w,直接枚举集合的子集DP或是暴力做 N N N次子集卷积复杂度分别为 O ( 3 N ) \mathcal O(3^N) O(3N)或 O ( 2 N ⋅ N 2 ) \mathcal O(2^N\cdot N^2) O(2N⋅N2),都不好通过。考虑关注一下性质,显然若干个合法的不交集合的并也是合法的,那么考虑已经求出了所有的能够划分为 i i i个合法非空集合的集合,对于每个集合 S S S,它能够划分为 i + 1 i+1 i+1个非空集合当且仅当它自身是合法的且存在一个 T ⊆ S T \subseteq S T⊆S使得 T T T能够划分为 i i i个非空集合,做 O ( N ) O(N) O(N)次高维前缀和即可。
单组数据时间复杂度为 O ( 2 N ⋅ N 2 ) \mathcal O(2^N\cdot N^2) O(2N⋅N2)。
#include <bits/stdc++.h>
using namespace std;
namespace SETS {
int fa[20];
void init(int n) {
for(int i=1;i<=n;i++) fa[i]=i;
}
int find_father(int x) {
return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}
void merge(int x,int y) {
x=find_father(x);y=find_father(y);
if (x==y) return;
fa[x]=y;
}
}
int p[20];
int col[20],cnt[20];
bool f[1<<18];
void pre(int n) {
for(int i=1;i<(1<<n);i++) {
memset(cnt,0,sizeof(cnt));
for(int j=1;j<=n;j++)
if ((i>>(j-1))&1) {
cnt[col[j]]++;
cnt[col[p[j]]]--;
}
bool ok=1;
for(int j=1;j<=n;j++)
if (cnt[j]) {
ok=0;
break;
}
f[i]=ok;
}
}
void fwt(int *p,int len) {
for(int i=1;i<len;i<<=1)
for(int j=0;j<len;j++)
if (j&i) p[j]+=p[j^i];
}
int g[1<<18];
int main() {
int cases;
scanf("%d",&cases);
for(;cases;cases--) {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
SETS::init(n);
for(int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
SETS::merge(x,y);
}
for(int i=1;i<=n;i++) col[i]=SETS::find_father(i);
pre(n);
int ans=n-1;
for(;;) {
for(int i=0;i<(1<<n);i++) g[i]=f[i];
fwt(g,1<<n);
for(int i=0;i<(1<<n);i++) f[i]=(f[i]&&g[i]>1);
if (!f[(1<<n)-1]) break;
ans--;
}
printf("%d\n",ans);
}
return 0;
}
/*
1
6 0
2 1 4 5 3 6
*/
Buying a New String
略
Binary Land
经典的双栈模拟队列。
考虑没有删除的做法,显然可以维护一个 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]起点在 ( 1 , k ) (1,k) (1,k),终点在 ( i , j ) (i,j) (i,j)的路径数目,转移直接计算即可。
再考虑有删除的做法,注意到我们可以快速合并两个 D P DP DP数组,那么我们随便选一行 m i d mid mid,维护两个DP数组 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]和 G [ i ′ ] [ j ′ ] [ k ′ ] G[i'][j'][k'] G[i′][j′][k′],分别表示起点在 ( m i d + 1 , k ) (mid+1,k) (mid+1,k),终点在 ( i , j ) (i,j) (i,j)和起点在 ( i ′ , j ′ ) (i',j') (i′,j′),终点在 ( m i d , k ) (mid,k) (mid,k)的路径数目,查询的时候可以直接合并。插入操作就在 F F F最后加入一行,删除操作如果没有删空 m i d mid mid前面的就直接删,否则取 m i d mid mid为当前最后一行重