IOI2020集训队作业-21 (CF590E, AGC031D, AGC026E)

A - CF590E Birthday

题意

n n n个互不相同的字符串,你需要选出这些字符串的一个子集,使得这个子集内不存在两个不同的字符串 s , t s,t s,t,满足 s s s t t t的子串。问这个子集最多能包含多少个元素,并输出方案。 n ≤ 750 n\le 750 n750,字符串的长度之和不超过 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 st(这个定义显然满足自反性、非对称性和传递性)。若 s ≰ t s\not\le t st并且 t ≰ s t\not\le s ts我们就称 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 ab 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 ap表示第 i i i个元素为 a p i a_{p_i} api的序列,令 p − 1 p^{-1} p1为满足 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)=qp1

算出序列的前若干项:

21-1

观察发现序列中的每一项都可以写成 A i B i A i − 1 A_iB_iA_i^{-1} AiBiAi1的形式:

21-2

并且 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=qp1q1p。观察: 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=A8B8A81A7B71A71,由于 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=A7B8B71A7=A7B2B11A71,也就是说 A 9 = A 3 ⋅ A 7 A_9 = A_3 \cdot A_7 A9=A3A7 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(tZ) 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=(qp1q1p)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} a66k1+1 a 6 ⌊ k − 1 6 ⌋ + 2 a_{6\lfloor { k-1\over 6 } \rfloor + 2} a66k1+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 ia都在第 i i ib之前出现。此时最优的答案一定是abababab……。可以直接贪心求出最长的能选的abababab……
  • 如果这一段开头的字符是b,那么对于任意 i i i,第 i i ia都在第 i i ib之后出现。假设最优策略中我们选了第 i i ib和第 i i ia,那么我们一定会选第 i + 1 i+1 i+1b和第 i + 1 i+1 i+1a,因为它们的位置关系一定形如 ⋯ 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 bibi+1aiai+1,选上 b i + 1 b_{i+1} bi+1一定会更优。故而,我们的选择一定是最后的 x x xa和最后的 x x xb。直接枚举 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值