【学习笔记】AGC055

A - ABC Identity

如果只有 A A A, B B B两种字符的话,我们发现要寻找 p ∈ [ 1 , n ] p\in [1,n] p[1,n],使得 [ 1 : p ] [1:p] [1:p] A A A的数目与 [ p + 1 : n ] [p+1:n] [p+1:n] B B B的数目相同。

如果有 A , B , C A,B,C A,B,C三种字符,我们可以先将 A , B A,B A,B分离出来,再将 B , C B,C B,C分离出来,最后把 A , C A,C A,C分离出来,这样最后会生成 8 8 8个子序列 然后无法通过

神谕告诉我们, A , B , C A,B,C A,B,C三种字符一共只有 6 6 6种本质不同的排列,因此我们可以考虑把原序列分成长度为 n n n 3 3 3段,从每一段中分别选取一个字符构成 A , B , C A,B,C A,B,C的排列,最后把相同的排列放在一起即可。猜一下结论,这显然是有解的。

这种题还是要多尝试

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
vector<int>v[3][3];
string s;
int n,res[600005];
int has(int x,int y,int z){
	return x*2+(y-(x<y))*1;
}
int main(){
	cin>>n>>s;
	for(int i=0;i<3*n;i++){
		v[i/n][s[i]-'A'].pb(i);
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<3;j++){
			for(int k=0;k<3;k++){
				for(int l=0;l<3;l++){
					if(v[0][j].size()&&v[1][k].size()&&v[2][l].size()&&j!=k&&k!=l&&j!=l){
						res[v[0][j].back()]=has(j,k,l);
						res[v[1][k].back()]=has(j,k,l);
						res[v[2][l].back()]=has(j,k,l);
						v[0][j].pop_back();
						v[1][k].pop_back();
						v[2][l].pop_back();
					}
				}
			}
		}
	}for(int i=0;i<3*n;i++)cout<<res[i]+1;
}

B - ABC Supremacy

如果只考虑 S S S怎么生成 T T T的话,怎么做都是 O ( n 2 ) O(n^2) O(n2)的。

但是操作是可逆的,因此判断 S , T S,T S,T同构的一个充分条件是它们都能到达一个相同的状态 P P P。所以我们只要求出 S , T S,T S,T的最小表示即可,这样一个字符串生成的表示是唯一的,就不用担心上述问题了。

显然我们要凑出尽量多的 A B C ABC ABC串(这里指轮换),并且每次操作相当于将 A B C ABC ABC串这个整体挪到前面,然后把 A A A放在最前面。那么 B C BC BC是固定的吗?如果 B C BC BC不是固定的,这个问题也比较烦恼。

不过我们可以把最小表示的定义换成 得到最多的 A B C ABC ABC轮换组,那么 B C BC BC就肯定是固定的了。

复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
int n;
string s,t;
vector<char>solve(string s){
	vector<char>v;
	for(int i=0;i<s.size();i++){
		v.pb(s[i]);
		if(v.size()>=3&&(v[v.size()-3]=='A'&&v[v.size()-2]=='B'&&v[v.size()-1]=='C'||v[v.size()-3]=='B'&&v[v.size()-2]=='C'&&v[v.size()-1]=='A'||v[v.size()-3]=='C'&&v[v.size()-2]=='A'&&v[v.size()-1]=='B')){
			v.pop_back();
			v.pop_back();
			v.pop_back();
		}
	}return v;
}
int main(){
	cin>>n>>s>>t;
	cout<<(solve(s)==solve(t)?"YES":"NO");
}

C - Weird LIS

如果我们能思考清楚 { A i } \{A_i\} {Ai}合法的充要条件,那么这道题也就解决了。

或者说能建立双射然后计数也行

思路其实并不困难,不过可能需要猜几个结论。

1.1 1.1 1.1 如果 A i A_i Ai全部等于 K K K,猜测 K ≤ ⌊ n 2 ⌋ K\le \lfloor\frac{n}{2}\rfloor K2n,这还是比较容易看出来。
1.2 1.2 1.2 如果 K K K K − 1 K-1 K1同时存在,那么 A i = K − 1 A_i=K-1 Ai=K1的那些点是固定的,我们要在所有 A i = K A_i=K Ai=K的连续段中挑选一段接在固定的数之间,那么根据 1.1 1.1 1.1的推论,这一段的长度不能超过 ⌊ l 2 ⌋ \lfloor\frac{l}{2}\rfloor 2l l l l表示连续段长度),我们猜测对于更小的情况也是取得到的,因此 ∑ ⌊ l i 2 ⌋ + c n t K − 1 ≥ K \sum{\lfloor\frac{l_i}{2}\rfloor}+cnt_{K-1}\ge K 2li+cntK1K,并且 c n t K − 1 ≤ K cnt_{K-1}\le K cntK1K

这个向下取整好像不太妙,先咕了

复杂度 O ( n 2 ) O(n^2) O(n2)。不过要注意特判 A i = n − 1 A_i=n-1 Ai=n1的情况。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N=5005;
int n,m,mod;
ll fac[N],inv[N],res;
ll pw(ll x,ll y=mod-2){
	ll z(1);
	for(;y;y>>=1){
		if(y&1)z=z*x%mod;
		x=x*x%mod;
	}return z;
}
void init(int n){
	fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
	inv[n]=pw(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
}
ll binom(int x,int y){
	if(x<0||y<0||x<y)return 0;
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	cin>>n>>m>>mod,init(n+1);
	for(int x=1;x<n;x++){
		for(int y=0;y<=(n-x)/2;y++){
			int L=max(3,x),R=min(m,x+y);
			if(L<=R)res=(res+binom(x+y,x)*binom(x+1,n-x-2*y)%mod*(R-L+1))%mod;
		}
	}
	for(int i=2;i<=min(m,n/2);i++)res++;
	if(m==n-1)res++;
	cout<<res%mod;
}

D - ABC Ultimatum

先考虑怎么判断给定串合法。

好像没什么思路先咕了

不过这题还是有学习的价值的,我们可以照着结论来翻译一下

S a ( i ) , S b ( i ) , S c ( i ) S_a(i),S_b(i),S_c(i) Sa(i),Sb(i),Sc(i)表示 1 ∼ i 1\sim i 1i a , b , c a,b,c a,b,c的个数, M a = max ⁡ ( S b ( i ) − S a ( i ) ) , M b = max ⁡ ( S c ( i ) − S b ( i ) ) , M c = max ⁡ ( S a ( i ) − S c ( i ) ) M_a=\max (S_b(i)-S_a(i)),M_b=\max(S_c(i)-S_b(i)),M_c=\max(S_a(i)-S_c(i)) Ma=max(Sb(i)Sa(i)),Mb=max(Sc(i)Sb(i)),Mc=max(Sa(i)Sc(i)),则 S S S是好的当且仅当 M a + M b + M c ≤ n M_a+M_b+M_c\le n Ma+Mb+Mcn

必要性应该很显然,可以猜一个结论,或者打表证明这是充要的。

然后暴力复杂度 O ( n 7 ) O(n^7) O(n7)。但是很显然可以省去一维状态,因此就可以在 O ( n 6 ) O(n^6) O(n6)时间内通过了。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int mod=998244353;
int n,dp[17][17][17][17][17][17],res;
string s;
void add(int &x,int y){
	if((x+=y)>=mod)x-=mod;
}
int main(){
	cin>>n>>s;dp[0][0][0][0][0][0]=1;
	for(int i=0;i<3*n;i++){
		for(int a=0;a<=n;a++){
			for(int b=0;b<=n;b++){
				int c=i-a-b;if(c>n||c<0)continue;
				for(int j=0;j<=n;j++){
					for(int k=0;k<=n;k++){
						for(int l=0;l<=n;l++){
							int tmp=dp[a][b][c][j][k][l];
							if(s[i]=='A'||s[i]=='?'){
								add(dp[a+1][b][c][j][k][max(l,a+1-c)],tmp);
							}if(s[i]=='B'||s[i]=='?'){
								add(dp[a][b+1][c][max(b+1-a,j)][k][l],tmp);
							}if(s[i]=='C'||s[i]=='?'){
								add(dp[a][b][c+1][j][max(c+1-b,k)][l],tmp);
							}
						}
					}
				}
			}
		}
	}
	for(int i=0;i<=n;i++){
		for(int j=0;j<=n;j++){
			for(int k=0;k<=n;k++){
				if(i+j+k<=n)add(res,dp[n][n][n][i][j][k]);
			}
		}
	}cout<<res;
}

E - Set Merging

场上无一人AC

构造题。考虑将问题抽象出来。

大概思路是,构造一个排列 { p i } \{p_i\} {pi},初始 p i = i p_i=i pi=i,合并 S i , S i + 1 S_i,S_{i+1} Si,Si+1就等价于交换 p i , p i + 1 p_i,p_{i+1} pi,pi+1。那么相当于要求一个排列 p p p,使其能导出 { l i } , { r i } \{l_i\},\{r_i\} {li},{ri}并且逆序对最小。

然后把该确定的位置确定,剩下的猜一个结论让逆序对最小。

我们只需要发现,对于不能确定的位置,一定满足 l i = l i + 1 , r i − 1 = r i l_{i}=l_{i+1},r_{i-1}=r_i li=li+1,ri1=ri,换句话说 p i ∈ [ l i , r i ] p_i\in [l_i,r_i] pi[li,ri],又因为 l i , r i l_i,r_i li,ri都是单增的,因此我们不难猜出结论:只要填区间 [ l i , r i ] [l_i,r_i] [li,ri]中最小的并且没有被选择的数即可,这显然也满足了逆序对数目最小。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
const int N=5e5+5;
int n,L[N],R[N],p[N],bit[N];
ll res;
set<int>S;
void add(int x){
	for(;x<=n;x+=x&-x)bit[x]++;
}
int ask(int x){
	int res(0);
	for(;x;x-=x&-x){
		res+=bit[x];
	}
	return res;
}
void ins(int x,int y){
	if(!p[x]){
		if(!S.count(y)){
			cout<<-1;
			exit(0);
		}
		p[x]=y;
		S.erase(y);
	}
	else if(p[x]!=y){
		cout<<-1;
		exit(0);
	}
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
	cin>>n;for(int i=1;i<=n;i++)cin>>L[i]>>R[i];
	for(int i=1;i<=n;i++)S.insert(i);
	ins(n,L[n]);
	for(int i=n-1;i>=1;i--){
		if(L[i]>L[i+1]){
			cout<<-1;
			return 0;
		}
		if(L[i]<L[i+1]){
			ins(i,L[i]);
		}
	}
	ins(1,R[1]);
	for(int i=2;i<=n;i++){
		if(R[i]<R[i-1]){
			cout<<-1;
			return 0;
		}
		if(R[i]>R[i-1]){
			ins(i,R[i]);
		}
	}
	for(int i=1;i<=n;i++){
		if(!p[i]){
			auto it=S.lower_bound(L[i]);
			if(it==S.end()||*it>R[i]){
				cout<<-1;
				return 0;
			}
			ins(i,*it);
		}
	}
	for(int i=1;i<=n;i++){
		add(p[i]);
		res+=i-ask(p[i]);
	}cout<<res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值