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

雨燕双飞 提交于 2019-12-29 23:20:45

A - CF590E Birthday

题意

nn个互不相同的字符串,你需要选出这些字符串的一个子集,使得这个子集内不存在两个不同的字符串s,ts,t,满足sstt的子串。问这个子集最多能包含多少个元素,并输出方案。n750n\le 750,字符串的长度之和不超过10710^7

Sol

sstt的子串等价于ttss的某一个前缀的后缀。由于后缀关系具有传递性(aabb的后缀,bbcc的后缀可推出aacc的后缀),所以只需要求出所有的“ttss的某一个前缀的最长后缀”的关系,再跑一遍传递闭包就可以得到“所有的ttss的子串”的关系。

将字符串作为元素,定义偏序关系\le为:若sstt的子串则有sts\le t(这个定义显然满足自反性、非对称性和传递性)。若s≰ts\not\le t并且t≰st\not\le s我们就称sstt为不可比较的。题目要求我们求的就是一个最大的子集,满足集合中的元素两两不可比较,也就是求最长反链的大小。

由Dilworth’s theorem的证明过程我们可以得到这样的一个算法:对于一个偏序集SS,构造一个二分图G=(X,Y,E)G=(X,Y,E),其中X=Y=SX=Y=S,而从XXaa点到YYbb点的边存在,当且仅当aba\le baba\not=b。求出这张图的一个最大匹配MM,以及一个最小顶点覆盖CC,要求满足CC中的任意一个顶点都有邻边在MM中。令AA为在XX中对应的点和在YY中对应的点都不属于CC的点组成的集合,则AA为最长反链。

注意这道题的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

apa\cdot p表示第ii个元素为apia_{p_i}的序列,令p1p^{-1}为满足qpi=iq_{p_i}=i的序列qq。则f(p,q)=qp1f(p,q) = qp^{-1}

算出序列的前若干项:

21-1

观察发现序列中的每一项都可以写成AiBiAi1A_iB_iA_i^{-1}的形式:

21-2

并且B7=B1,B8=B2B_7 = B_1,B_8 = B_2,而A7=A8=qp1q1pA_7 = A_8 = qp^{-1}q^{-1}p。观察:a9=A8B8A81A7B71A71a_9 = A_8B_8A_8^{-1} \cdot A_7B_7^{-1}A_7^{-1},由于A8=A7A_8=A_7,所以a9=A7B8B71A7=A7B2B11A71a_9 = A_7B_8B_7^{-1}A_7 = A_7 B_2B_1^{-1} A_7^{-1},也就是说A9=A3A7A_9 = A_3 \cdot A_7B9=B3B_9=B_3

所以对于某一个i=6t+1(tZ)i=6t + 1(t\in \mathbb{Z})Ai=Ai+1=(qp1q1p)t,Bi=p,Bi+1=qA_i = A_{i+1}= (qp^{-1}q^{-1}p)^{t},B_i=p,B_{i+1}=q。某个置换进行kk次后得到的置换是可以O(n)O(n)算的,方法是找出每一个环(令环长为LL),每一个元素最终会置换到的元素是环上距离它为k(modL)k\pmod L的元素。算出a6k16+1a_{ 6\lfloor {k-1\over 6} \rfloor + 1}a6k16+2a_{6\lfloor { k-1\over 6 } \rfloor + 2}直接暴力算出aka_k就可以了。

#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

将原字符串划分成尽可能多的段,满足每一段aabb的数量相同。显然我们的操作对于每一段是独立的。考虑如何求出一段的最优答案:

  • 如果这一段开头的字符是a,那么对于任意ii,第iia都在第iib之前出现。此时最优的答案一定是abababab……。可以直接贪心求出最长的能选的abababab……
  • 如果这一段开头的字符是b,那么对于任意ii,第iia都在第iib之后出现。假设最优策略中我们选了第iib和第iia,那么我们一定会选第i+1i+1b和第i+1i+1a,因为它们的位置关系一定形如bibi+1aiai+1\cdots b_i \cdots b_{i+1} \cdots a_i \cdots a_{i+1} \cdots,选上bi+1b_{i+1}一定会更优。故而,我们的选择一定是最后的xxa和最后的xxb。直接枚举xx然后取最优的解,复杂度O(n2)O(n^2)

求出每一段的答案后,从后往前开始贪心:如果当前这一段大于等于后面的段得到的最优答案,就将这一段拼在后面的段的答案的前面,否则就扔掉这一段。总时间复杂度O(n2)O(n^2)

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;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!