A - CF590E Birthday
题意
有 n n n个互不相同的字符串,你需要选出这些字符串的一个子集,使得这个子集内不存在两个不同的字符串 s , t s,t s,t,满足 s s s是 t t t的子串。问这个子集最多能包含多少个元素,并输出方案。 n ≤ 750 n\le 750 n≤750,字符串的长度之和不超过 1 0 7 10^7 107。
Sol
s s s是 t t t的子串等价于 t t t是 s s s的某一个前缀的后缀。由于后缀关系具有传递性( a a a是 b b b的后缀, b b b是 c c c的后缀可推出 a a a是 c c c的后缀),所以只需要求出所有的“ t t t是 s s s的某一个前缀的最长后缀”的关系,再跑一遍传递闭包就可以得到“所有的 t t t是 s s s的子串”的关系。
将字符串作为元素,定义偏序关系 ≤ \le ≤为:若 s s s为 t t t的子串则有 s ≤ t s\le t s≤t(这个定义显然满足自反性、非对称性和传递性)。若 s ≰ t s\not\le t s≤t并且 t ≰ s t\not\le s t≤s我们就称 s s s和 t t t为不可比较的。题目要求我们求的就是一个最大的子集,满足集合中的元素两两不可比较,也就是求最长反链的大小。
由Dilworth’s theorem的证明过程我们可以得到这样的一个算法:对于一个偏序集 S S S,构造一个二分图 G = ( X , Y , E ) G=(X,Y,E) G=(X,Y,E),其中 X = Y = S X=Y=S X=Y=S,而从 X X X的 a a a点到 Y Y Y的 b b b点的边存在,当且仅当 a ≤ b a\le b a≤b且 a ≠ b a\not=b a=b。求出这张图的一个最大匹配 M M M,以及一个最小顶点覆盖 C C C,要求满足 C C C中的任意一个顶点都有邻边在 M M M中。令 A A A为在 X X X中对应的点和在 Y Y Y中对应的点都不属于 C C C的点组成的集合,则 A A A为最长反链。
注意这道题的Trie的深度很大,在cf直接递归遍历会爆栈,必须使用非递归的实现方式。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
int G[755][755],n;
int atk[755],num;
namespace AC_Automation {
const int N=1e7+10;
const int M=1e8;
int ch[N][2];
int fail[N],ncnt=1,rt=1;
int id[N],par[N];
void insert(char *str,int len,int _id) {
int cur=rt;
for(int i=0;i<len;++i) {
int c=str[i]-'a';
if(!ch[cur][c]) ch[cur][c]=++ncnt;
cur=ch[cur][c];
}
if(id[cur]) num++,atk[_id]=1;
else id[cur]=_id;
}
int buc[755];
int stk[N],top;
int cur[N];
void dfs() {
stk[top=1]=rt;
while(top) {
int u=stk[top];
if(cur[u]==0) {
cur[u]=1;
if(id[par[u]]) buc[id[par[u]]]++;
if(id[u]) {
for(int j=1;j<=n;++j)
if(buc[j]) G[id[u]][j]=1;
buc[id[u]]++;
}
}
if(cur[u]==1) {
cur[u]=2;
if(ch[u][0]<M) {
stk[++top]=ch[u][0];
continue;
}
}
if(cur[u]==2) {
cur[u]=3;
if(ch[u][1]<M) {
stk[++top]=ch[u][1];
continue;
}
}
if(cur[u]==3) {
if(id[u]) buc[id[u]]--;
if(id[par[u]]) buc[id[par[u]]]--;
top--;
}
}
// if(id[par[u]]) buc[id[par[u]]]++;
// if(id[u]) {
// for(int j=1;j<=n;++j)
// if(buc[j]) G[id[u]][j]=1;
// buc[id[u]]++;
// }
if(id[u]) {
if(lastid) G[id[u]][lastid]=1;
if(id[par[u]]) G[id[u]][id[par[u]]]=1;
lastid=id[u];
}
// for(int i=0;i<2;++i) if(ch[u][i]<M) dfs(ch[u][i]);
// if(id[u]) buc[id[u]]--;
// if(id[par[u]]) buc[id[par[u]]]--;
}
queue<int> que;
void sol_str() {
for(int i=0;i<2;++i) if(ch[rt][i]) {
fail[ch[rt][i]]=par[ch[rt][i]]=rt;
que.push(ch[rt][i]);
}
else ch[rt][i]=rt+M;
while(!que.empty()) {
int u=que.front(); que.pop();
if(id[fail[u]]) par[u]=fail[u];
else par[u]=par[fail[u]];
for(int i=0;i<2;++i)
if(ch[u][i]) fail[ch[u][i]]=ch[fail[u]][i]%M,que.push(ch[u][i]);
else ch[u][i]=ch[fail[u]][i]%M+M;
}
dfs();
}
}
using AC_Automation::insert;
using AC_Automation::sol_str;
int used[755][755];
int To[755][2],inC[755][2];
int S,T,ncnt;
namespace Flow {
const int N=1550;
int head[N],dep[N],cur[N],ecnt;
struct ed { int to,next,f; };
vector<ed> e;
inline void init() { memset(head,-1,sizeof(head)); }
inline void ad(int x,int y,int f) {
e.PB((ed){y,head[x],f}); head[x]=e.size()-1;
e.PB((ed){x,head[y],0}); head[y]=e.size()-1;
}
queue<int> que;
bool bfs() {
for(int i=1;i<=ncnt;++i) dep[i]=-1,cur[i]=head[i];
while(!que.empty()) que.pop();
que.push(S),dep[S]=0;
while(!que.empty()) {
int u=que.front(); que.pop();
if(u==T) return 1;
for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to; if(dep[v]!=-1) continue;
dep[v]=dep[u]+1,que.push(v);
}
}
return 0;
}
int dfs(int u,int f) {
if(u==T||!f) return f; int tmp,ret=0;
for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(f,e[k].f)))) {
ret+=tmp,f-=tmp;
e[k].f-=tmp,e[k^1].f+=tmp;
if(!f) break;
}
}
return ret;
}
int work() { int ans=0; while(bfs()) ans+=dfs(S,1e9); return ans; }
void work_out() {
for(int i=n+1;i<=n+n;++i)
for(int k=head[i];~k;k=e[k].next) if(e[k].f) {
int v=e[k].to;
if(v<=n) {
used[v][i-n]=1;
To[v][0]=i-n;
To[i-n][1]=v;
}
}
}
}
char str[10000010];
int vis[755][2];
void dfs(int u,int p) {
if(vis[u][p]) return;
vis[u][p]=1;
for(int v=1;v<=n;++v) if((!p&&G[u][v])||(p&&G[v][u])) {
inC[v][p^1]=1;
dfs(To[v][p^1],p);
}
}
int main() {
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
rd(n);
for(int i=1;i<=n;++i) scanf("%s",str),insert(str,strlen(str),i);
sol_str();
for(int i=1;i<=n;++i)
for(int k=1;k<=n;++k) if(G[i][k])
for(int j=1;j<=n;++j) G[i][j]|=G[k][j];
Flow::init();
S=n+n+1,T=n+n+2,ncnt=T;
for(int i=1;i<=n;++i) if(!atk[i])
for(int j=1;j<=n;++j) if(!atk[j]&&G[i][j])
Flow::ad(i,j+n,1);
for(int i=1;i<=n;++i) if(!atk[i]) Flow::ad(S,i,1),Flow::ad(i+n,T,1);
int ans=n-num-Flow::work();
Flow::work_out();
for(int i=1;i<=n;++i) if(!atk[i])
for(int d=0;d<2;++d)
if(!To[i][d]) dfs(i,d);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(To[i][0]==j&&!(inC[i][0]||inC[j][1]))
dfs(i,0);
printf("%d\n",ans);
for(int i=1;i<=n;++i) if(!atk[i]&&!(inC[i][0]|inC[i][1])) printf("%d ",i);
return 0;
}
B - AGC031D A Sequence of Permutations
Sol
令 a ⋅ p a\cdot p a⋅p表示第 i i i个元素为 a p i a_{p_i} api的序列,令 p − 1 p^{-1} p−1为满足 q p i = i q_{p_i}=i qpi=i的序列 q q q。则 f ( p , q ) = q p − 1 f(p,q) = qp^{-1} f(p,q)=qp−1。
算出序列的前若干项:
观察发现序列中的每一项都可以写成 A i B i A i − 1 A_iB_iA_i^{-1} AiBiAi−1的形式:
并且 B 7 = B 1 , B 8 = B 2 B_7 = B_1,B_8 = B_2 B7=B1,B8=B2,而 A 7 = A 8 = q p − 1 q − 1 p A_7 = A_8 = qp^{-1}q^{-1}p A7=A8=qp−1q−1p。观察: a 9 = A 8 B 8 A 8 − 1 ⋅ A 7 B 7 − 1 A 7 − 1 a_9 = A_8B_8A_8^{-1} \cdot A_7B_7^{-1}A_7^{-1} a9=A8B8A8−1⋅A7B7−1A7−1,由于 A 8 = A 7 A_8=A_7 A8=A7,所以 a 9 = A 7 B 8 B 7 − 1 A 7 = A 7 B 2 B 1 − 1 A 7 − 1 a_9 = A_7B_8B_7^{-1}A_7 = A_7 B_2B_1^{-1} A_7^{-1} a9=A7B8B7−1A7=A7B2B1−1A7−1,也就是说 A 9 = A 3 ⋅ A 7 A_9 = A_3 \cdot A_7 A9=A3⋅A7, B 9 = B 3 B_9=B_3 B9=B3。
所以对于某一个 i = 6 t + 1 ( t ∈ Z ) i=6t + 1(t\in \mathbb{Z}) i=6t+1(t∈Z), A i = A i + 1 = ( q p − 1 q − 1 p ) t , B i = p , B i + 1 = q A_i = A_{i+1}= (qp^{-1}q^{-1}p)^{t},B_i=p,B_{i+1}=q Ai=Ai+1=(qp−1q−1p)t,Bi=p,Bi+1=q。某个置换进行 k k k次后得到的置换是可以 O ( n ) O(n) O(n)算的,方法是找出每一个环(令环长为 L L L),每一个元素最终会置换到的元素是环上距离它为 k ( m o d L ) k\pmod L k(modL)的元素。算出 a 6 ⌊ k − 1 6 ⌋ + 1 a_{ 6\lfloor {k-1\over 6} \rfloor + 1} a6⌊6k−1⌋+1和 a 6 ⌊ k − 1 6 ⌋ + 2 a_{6\lfloor { k-1\over 6 } \rfloor + 2} a6⌊6k−1⌋+2直接暴力算出 a k a_k ak就可以了。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1e5+10;
int n;
void mul(int *a,int *p) {
static int b[N];
for(int i=1;i<=n;++i) b[i]=a[p[i]];
for(int i=1;i<=n;++i) a[i]=b[i];
}
int vis[N];
void Pow(int *a,int k) {
static int b[N],c[N],m;
for(int i=1;i<=n;++i) vis[i]=0;
for(int i=1;i<=n;++i) if(!vis[i]) {
m=0; int cur=i;
while(!vis[cur]) vis[b[m++]=cur]=1,cur=a[cur];
for(int i=0;i<m;++i) c[b[i]]=b[(i+k)%m];
}
for(int i=1;i<=n;++i) a[i]=c[i];
}
int p[N],q[N],rp[N],rq[N];
int a[N],ra[N],b[N],c[N];
int main() {
int L; rd(n),rd(L),L--;
for(int i=1;i<=n;++i) rd(p[i]),rp[p[i]]=i;
for(int i=1;i<=n;++i) rd(q[i]),rq[q[i]]=i;
for(int i=1;i<=n;++i) a[i]=i;
mul(a,q),mul(a,rp),mul(a,rq),mul(a,p);
Pow(a,L/6); L%=6;
for(int i=1;i<=n;++i) ra[a[i]]=i;
for(int i=1;i<=n;++i) b[i]=a[i];
mul(b,q),mul(b,ra);
mul(a,p),mul(a,ra);
while(L--) {
for(int i=1;i<=n;++i) ra[a[i]]=i,c[i]=b[i];
mul(c,ra);
for(int i=1;i<=n;++i) a[i]=b[i],b[i]=c[i];
}
for(int i=1;i<=n;++i) printf("%d ",a[i]);
return 0;
}
C - AGC026E Synchronized Subsequence
Sol
将原字符串划分成尽可能多的段,满足每一段 a a a和 b b b的数量相同。显然我们的操作对于每一段是独立的。考虑如何求出一段的最优答案:
- 如果这一段开头的字符是
a
,那么对于任意 i i i,第 i i i个a
都在第 i i i个b
之前出现。此时最优的答案一定是abababab……
。可以直接贪心求出最长的能选的abababab……
- 如果这一段开头的字符是
b
,那么对于任意 i i i,第 i i i个a
都在第 i i i个b
之后出现。假设最优策略中我们选了第 i i i个b
和第 i i i个a
,那么我们一定会选第 i + 1 i+1 i+1个b
和第 i + 1 i+1 i+1个a
,因为它们的位置关系一定形如 ⋯ b i ⋯ b i + 1 ⋯ a i ⋯ a i + 1 ⋯ \cdots b_i \cdots b_{i+1} \cdots a_i \cdots a_{i+1} \cdots ⋯bi⋯bi+1⋯ai⋯ai+1⋯,选上 b i + 1 b_{i+1} bi+1一定会更优。故而,我们的选择一定是最后的 x x x个a
和最后的 x x x个b
。直接枚举 x x x然后取最优的解,复杂度 O ( n 2 ) O(n^2) O(n2)。
求出每一段的答案后,从后往前开始贪心:如果当前这一段大于等于后面的段得到的最优答案,就将这一段拼在后面的段的答案的前面,否则就扔掉这一段。总时间复杂度 O ( n 2 ) O(n^2) O(n2)。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
vector<string> s;
string S,ans,cur;
int n,m;
void sol(string &s) {
vector<int> p1,p2;
string t="";
for(int i=0;i<s.length();++i)
if(s[i]=='a') p1.PB(i);
else p2.PB(i);
if(s[0]=='a') {
int last=-1;
for(int i=0;i<p1.size();++i)
if(p1[i]>last) last=p2[i],t+="ab";
}
else {
for(int i=0;i<p1.size();++i) {
string p="";
int x=i,y=i;
while(1)
if(x<p1.size()&&(y==p2.size()||p1[x]<p2[y]))
p+='a',x++;
else if(y<p2.size())
p+='b',y++;
else break;
if(p>t) t=p;
}
}
s=t;
}
int main() {
rd(n);
cin>>S;
int cnt=0;
for(int i=0;i<n*2;++i) {
if(S[i]=='a') cnt++;
else cnt--;
cur+=S[i];
if(cnt==0) {
s.PB(cur);
cur="";
}
}
for(int i=0;i<s.size();++i) sol(s[i]);
ans=s.back();
for(int i=(int)s.size()-2;i>=0;--i) {
int flg=1;
for(int j=0;j<min(s[i].length(),ans.length());++j)
if(s[i][j]!=ans[j]) {
if(s[i][j]<ans[j]) flg=0;
break;
}
if(flg) ans=s[i]+ans;
}
cout<<ans;
return 0;
}