CSP模拟26

可做场,拜谢fengwu老师。

A. Reversi (AGC031B)

题目链接

一眼切了
设 $ dp_i $ 表示考虑到第 $ i$ 个石头的总方案数。
可由两种情况转移,不选择染色和选择染色,不染色直接由 $ dp_{i-1} $ 转移过来 ,染色由上一个和当前颜色相同的的石头转移过来,相当于把两个石子之间的染色。因为一个石子被染色后就不可以在给别的石子染色。
设 $ h_i $表示和当前石子颜色相同的最近的石子的位置。
则:

\[dp_i=dp_{i-1} + dp_{i-h[i]} \]

复杂度为 $ O(n) $

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector> 

#define int long long 

const int MAXN=5e6+10;
const int mod=1e9+7; 

using namespace std;

inline int read() {
	int f=1,x=0;
	char ch=getchar();
	while(ch>'9' || ch<'0') {
		if(ch=='-') {
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	} 
	return f*x;
}

int n; 
int a[MAXN];
int cnt[MAXN], beh[MAXN];
int dp[MAXN];

signed main() {
	
	n=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
		beh[i]=cnt[a[i]];
		cnt[a[i]]=i;
	}
	
	dp[0]=0, dp[1]=1;
	for(int i=2;i<=n;i++) {
		dp[i]=dp[i-1]%mod;
		if(beh[i]+1==i) continue;
		dp[i]=(dp[i]+dp[beh[i]])%mod;
	}
	
	printf("%lld",dp[n]%mod);
	
	return 0;
} 

B. Non-Decreasing Dilemma(CF1567E)

题目链接

比较普通的线段树维护,考虑维护区间左右两端の最长上升字串,对于完整地上升字串 ,设长度为 $ len$ 则对答案地贡献为 $ len(len-1)/2 $ ,合并时判断即可。
复杂度:修改为 $ O( \log n) $ ,查询为 $ O(\log n) $ ,总复杂度为 $ O(q\log n)$

点击查看代码
#include <iostream>
#include <cstdio>

#define int long long

const int MAXN=2e5+10;

using namespace std;

inline int read() {
	int f=1,x=0;
	char ch=getchar();
	while(ch>'9' || ch<'0') {
		if(ch=='-') {
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	} 
	return f*x;
}

int n,q;
int a[MAXN];
struct Tree {
	int l,r;
	int vl,vr;
	int ll,lr;
	int sum,len;
}t[MAXN<<2];

void push_up(int k) {
	t[k].len=t[k<<1].len+t[k<<1|1].len;
	t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
	t[k].vl=t[k<<1].vl ,t[k].vr=t[k<<1|1].vr;
	t[k].ll=t[k<<1].ll , t[k].lr=t[k<<1|1].lr; 
	
	if(t[k<<1].vr<=t[k<<1|1].vl) {
		int len=t[k<<1].lr+t[k<<1|1].ll;
		if(t[k<<1].lr!=t[k<<1].len && t[k<<1|1].ll!=t[k<<1|1].len) {
			t[k].sum+=(len*(len-1)/2);
		}
		if(t[k<<1].ll==t[k<<1].len) {
			t[k].ll=len;
		}
		if(t[k<<1|1].lr==t[k<<1|1].len) {
			t[k].lr=len;
		}
	}
	else {
		if(t[k<<1].lr!=t[k<<1].len) {
			int len=t[k<<1].lr;
			t[k].sum+=(len*(len-1)/2);
		}
		if(t[k<<1|1].ll!=t[k<<1|1].len) {
			int len=t[k<<1|1].ll;
			t[k].sum+=(len*(len-1)/2);
		}
	}
}

void build(int k,int l,int r) {
	t[k].l=l ,t[k].r=r;
	if(l==r) {
		t[k].len=1;
		t[k].sum=0;
		t[k].vl=t[k].vr=a[l];
		t[k].ll=t[k].lr=1;
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r); 
	push_up(k);
}

void change(int k,int l,int r,int pos,int val) {
	if(l==r) {
		t[k].vl=t[k].vr=val;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid) change(k<<1,l,mid,pos,val);
	else change(k<<1|1,mid+1,r,pos,val);
	push_up(k);
}

Tree query(int k,int l,int r,int L,int R) {
	if(L==l && r==R) {
		return t[k];
	}
	int mid=(l+r)>>1;
	if(mid<L) {
		return query(k<<1|1,mid+1,r,L,R);
	}
	else if(mid>=R) {
		return query(k<<1,l,mid,L,R);
	}
	else {
		Tree x,y,res;
		x=query(k<<1,l,mid,L,mid);
		y=query(k<<1|1,mid+1,r,mid+1,R);
		
		res.len=x.len+y.len;
		res.sum=x.sum+y.sum;
		res.vl=x.vl ,res.vr=y.vr;
		res.ll=x.ll ,res.lr=y.lr;
		
		if(x.vr<=y.vl) {
			int len=x.lr+y.ll;
			if(x.lr!=x.len && y.ll!=y.len) {
				res.sum+=(len*(len-1)/2);
			}
			if(x.ll==x.len) {
				res.ll=len;
			}
			if(y.lr==y.len) {
				res.lr=len;
			}
		}
		else {
			if(x.lr!=x.len) {
				int len=x.lr;
				res.sum+=(len*(len-1)/2);
			}
			if(y.ll!=y.len) {
				int len=y.ll;
				res.sum+=(len*(len-1)/2);
			}
		}
		return res;
	}
}

void solve1() {
	for(int g=1;g<=q;g++) {
		int op=read();
		if(op==1) {
			int x=read() ,y=read();
			a[x]=y;
		}
		if(op==2) {
			int l=read(),r=read(),ans=0;
			int sum=1;
			for(int i=l+1;i<=r;i++) {
				if(a[i-1]<=a[i]) {
					sum++;
				}
				else {
					ans+=((sum-1)*sum)/2;
					sum=1;
				}
			} 
			ans+=(sum*(sum-1)/2);
			printf("%lld\n",ans+(r-l+1));
		}
	}
}

signed main() {
	
	n=read() ,q=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
	}

	build(1,1,n);
	
	for(int i=1;i<=q;i++) {
		int op=read();
		if(op==1) {
			int x=read() ,y=read();
			a[x]=y;
			change(1,1,n,x,y);
		}
		if(op==2) {
			int x=read() ,y=read();
			Tree res=query(1,1,n,x,y);
			int l=res.ll ,r=res.lr ,ans;
			
			if(res.sum==0 && l==res.len && r==res.len) ans=(l*(l-1)/2);
			else ans=res.sum+(l*(l-1)/2)+(r*(r-1))/2;
			
			printf("%lld\n",ans+(y-x+1));
		}
	}
	
	return 0;
} 

C. Synchronized Subsequence(AGC026E)

题目链接

考试时没有一点思路,就跳了,赛后打暴力,发现暴力被卡了,寄。
首先我们发现一个 \(a\)\(b\) 个数相同的字串可以单独考虑,和其他的字串不造成影响,我们设 \(a\)\(-1\) ,\(b\)\(+1\) ,也就是前缀和为零的一段字串可以单独考虑;
我们设 \(a_i\) 表示第 \(i\)\(a\) 出现的位置, \(b_i\) 定义类似。
发现这种字串可分为两种, 字串内的字符,全部要么 \(a_i > b_i\) ,要么 $ a_i < b_i $ ,我们分情况考虑。

1.\(a_i>b_i\)
删完后的串第一个字符一定是 \(a\) ,而且删完后的串一定形如 \(ababab....ababab\) ,如果有两个或者多个 \(a\) 连在一起,那么一定可以通过删点,变成 \(ab\) 的形式。 \(O(n)\)枚举就可以了;
2.\(a_i<b_i\)
删完后的串的第一个字符一定是 \(b\),且前几个字符一定是 \(b\)
我们想让更多的 \(b\) 尽量靠前,利用之前的前缀和,找到前缀和的最大值,设为 \(maxn\), 并且最后删完的串的前缀一定有 \(maxn\)\(b\) ,一个序列里可能有多个字符的前缀和为 \(maxn\) ,我们找其中后缀最大的就可以了。 \(O(n^2)\) 暴力就可以。

处理完所有子串后,考虑拼接,发现有一些字串最后没有被采用,例如 \(abab\)\(bbbbaaaa\) ,如果直接拼接为 \(ababbbbbaaaa\) ,显然直接选 \(bbbbaaaa\) 更大。

可以维护一个类似于单调栈的东西,设当前串为 \(s\) ,如果 $sta_{top}+h < h $,就 \(pop\) 掉。

最后的复杂度为 \(O(n^2)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <cstring> 

const int MAXN=6010;

using namespace std;

inline int read() {
	int f=1,x=0;
	char ch=getchar();
	while(ch>'9' || ch<'0') {
		if(ch=='-') {
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	} 
	return f*x;
}

int n,tot,cnt;
int sum[MAXN],p[MAXN];
int a[MAXN],b[MAXN],mp1[MAXN],mp2[MAXN];
char s[MAXN];
string ok[MAXN],sta[MAXN];
bool vis[MAXN];

void work(string ss) {
	int len=ss.size();
	ss=" "+ss;
	int sum1=0,sum2=0;
	for(int i=1;i<=len;i++) {
		if(ss[i]=='a') {
			a[++sum1]=i;
			mp1[i]=sum1;
		}
		else {
			b[++sum2]=i;
			mp2[i]=sum2;
		}
		vis[i]=0;
	}
	
	if(ss[1]=='a') {
		int tmp=0;
		for(int i=1;i<=len;i++) {
			if(vis[i]) continue;
			if(ss[i]=='a') {
				tmp++;
			}
			if(ss[i]=='b') {
				tmp=0;
			}
			if(tmp==2) {
				vis[a[mp1[i]]]=1;
				vis[b[mp1[i]]]=1;
				tmp--;
			}
		}
		string res;
		for(int i=1;i<=len;i++) {
			if(vis[i]) continue;
			res+=ss[i];
		}
		ok[++cnt]=res;
	}
	else {
		int maxn=0;
		for(int i=1;i<=len;i++) {
			if(ss[i]=='a') {
				sum[i]=sum[i-1]-1;
			}
			else {
				sum[i]=sum[i-1]+1;
			}
			maxn=max(sum[i],maxn);
		}
		string hz,hzm;
		int q;
		for(int i=len;i>=1;i--) {
			if(sum[i]==maxn) {
				if(hzm<hz) {
					hzm=hz;
					q=i;
				}
			}
			hz=ss[i]+hz;
		}
		
		for(int i=1;i<=q;i++) {
			if(ss[i]=='a') {
				vis[a[mp1[i]]]=1;
				vis[b[mp1[i]]]=1;
			}
		}
		string res;
		for(int i=1;i<=len;i++) {
			if(vis[i]) continue;
			res+=ss[i];
		}
		ok[++cnt]=res;
	}
}

int main() {
	
	n=read();
	scanf("%s",s+1);
	for(int i=1;i<=strlen(s+1);i++) {
		if(s[i]=='a') {
			sum[i]=sum[i-1]-1;
		}
		else {
			sum[i]=sum[i-1]+1;
		}
		if(sum[i]==0) {
			p[++tot]=i;
		}
	}
	
	for(int i=1;i<=tot;i++) {
		string ss;
		for(int j=p[i-1]+1;j<=p[i];j++) {
			ss=ss+s[j];
		}
		work(ss);
	}
	
	int top=0;
	for(int i=1;i<=cnt;i++) {
		string h=ok[i];
		while(top && h>sta[top]+h) {
			top--;
		}
		sta[++top]=h;
	}
	
	for(int i=1;i<=top;i++) {
		cout<<sta[i];
	}
	
	return 0;
} 

D. Star MST(CF1657E)

题目链接

神奇的图论计数,之前没有做过。第一次看题时,给我直接看蒙了,但现在回过头再看,其实也就那样。

首先看明白题 ,考虑一个菊花图,这个菊花图的边权和即为所有与 1 号点相连的边的边权和,他与最小生成树的边权和相等,
说明这个菊花图就是其中一棵最小生成树。

设 $w_{x,y} $ 为边 $ u->v$ 的边权 ,\(w_x\) 表示 \(x\)\(1\) 的边权。
设两个节点 \(u\)\(v\),我们发现,如果想要那个菊花图为一棵最小生成树,必须满足 \(w_{u,v} \geqslant \max(w_u,w_v)\) ,我们发现,对于一条边的边权,只受这么一个限制。外加一个 \(k\) 的限制。

所以我们可以根据这个来设置状态转移方程。

\(dp_{i,j}\) 表示,已经考虑了 \(i\) 个节点 ,这 \(i\) 个节点与 \(1\) 的连边边权最大为 \(j\) ,主要这 \(i\) 个点是随机的,而且这个 \(j\) 只是设置了一个上界,也就是说不一定真的有边权为 \(j\)

考虑转移,设 \(L\) 表示有 \(L\) 个点与 \(1\) 的连边的边权为 \(j\) ,枚举 \(L\) , 所以 \(dp_{i,j}\)\(dp_{i-L,j-1}\) 转移过来,首先选定这 \(L\) 个点 ,为 \(\dbinom{n-(i-L)}{L}\) ,再考虑连边,这些边共有 \((k-j+1)\) 种选择,考虑边的数量,首先是 \(L\) 个点互相连边,为 \(\frac{n(n-1)}{2}\) 条, 再考虑把这 \(L\) 个点和 剩下的 \((n-L-1)\) 个点连边,有 \((L*(n-L-1))\) 条,总贡献为 \((k-j+1)^{ \frac{n(n-1)}{2} +L*(n-L-1)}\).

所以状态转移方程为:$dp_{i,j}=\sum_{L=1}^{i-1} \tbinom{n-i+L}{L} (k-j+1)^{ \frac{n(n-1)}{2} +L*(n-L-1)} $

复杂度为 \(O(n^3)\)

点击查看代码
#include <iostream>
#include <cstdio>

#define int long long

const int mod=998244353;
const int MAXN=260;

using namespace std;

inline int read() {
	int f=1,x=0;
	char ch=getchar();
	while(ch>'9' || ch<'0') {
		if(ch=='-') {
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	} 
	return f*x;
}

int n,k;
int C[MAXN][MAXN];
int dp[MAXN][MAXN];

void init() {
	C[0][0]=1;
	for(int i=1;i<=n;i++) {
		C[i][0]=1;
		for(int j=1;j<=i;j++) {
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
		}
	}
}

int fpow(int x,int k) {
	int res=1;
	while(k) {
		if(k&1) res=res*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return res%mod;
}

signed main() {
	
	n=read() ,k=read();
	
	init();
	
	dp[1][0]=1;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=k;j++) {
			for(int l=0;l<i;l++) {
				dp[i][j]=(dp[i][j]+dp[i-l][j-1]%mod*C[n-i+l][l]%mod*
					fpow((k-j+1)%mod,l*(l-1)/2+l*(i-l-1))%mod)%mod;
			}
		}
	}
	
	printf("%lld",dp[n][k]%mod);
	
	return 0;
} 
posted @ 2023-08-20 16:30  Trmp  阅读(8)  评论(0编辑  收藏  举报
-->