CCF-CSP2021复赛题解(转载整理)

考前:

我的内心:

T1:骗30分

T2:骗15分

T3:骗15分

T4:暴力拿一点点分数

然后我想冲二等奖

考后:

我的内心:

T1:0

T2:0

T3:0

T4:0

好家伙。。。

前言:

个人感觉今年题目总体难度很大。。。

除了第3题,实在写不出题解。。。

但我把其他大佬的题解整理了一下

祝大家明天AK!

目录:

1.廊桥分配

2.括号序列

(不要问我3,4去哪里了,我还在写)

1.廊桥分配(airport)

//此题解来自于Youthjoy_Creator

这题贪心+堆

国际,国内航班分别计算。

假设我们有足够多的廊桥,我们可以让尽量多的飞机停在尽量靠前的地方,这样一个前缀和就可得到当某一区分配了 pp 个廊桥的地方了。

但是!!!

如何计算这玩意。

ai​,bi​≤100000000

离散化!

压一下,变成 2*10^5 的数据范围

用一个 priority_queue 维护当前可用的廊桥编号,用一个数组维护当前飞机如果停靠在廊桥上的话是几号。

以下假设这个优先队列是小根堆。

考虑每一个时间点,如果飞机飞入,取出队首,让这架飞机停靠。

如果飞机飞出,找到这架飞机停靠的廊桥然后压入。

做两遍这个操作就可以了。

时间复杂度:n log n(舒服)

//说句实话T1比T3难

好了,上代码

#include
#define N 100009
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long ll;
inline ll read(){
    ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
	while(isdigit(c)){x=x*10+c-'0';c=getchar();}
	return x*f;
}
ll n,m[2],rk[2][2*N],in[2][2*N],out[2][2*N],nw[2][2*N],ans[2][N],ANS;
struct node2{
	ll id,k;
	bool operator<(const node2&x)const{return k qu[2];
void solve(ll x){
	for(int i=1;i<=m[x];i++) qu[x].push(-i);
	for(int i=1;i<=2*m[x];i++){
		if(in[x][i]){
			ll u=-qu[x].top();qu[x].pop();
			nw[x][in[x][i]]=u;
			ans[x][u]++;
		}
		else qu[x].push(-nw[x][out[x][i]]),nw[x][out[x][i]]=0;
	}
	for(int i=1;i<=100000;i++) ans[x][i]+=ans[x][i-1];
}
int main(){
	//freopen("airport.in","r",stdin);
	//freopen("airport.out","w",stdout);
	n=read(),m[0]=read(),m[1]=read();
	for(int i=1;i<=m[0];i++) q[2*i-1].k=read(),q[2*i-1].id=2*i-1,q[2*i].k=read(),q[2*i].id=2*i;
	sort(q+1,q+2*m[0]+1);
	for(int i=1;i<=2*m[0];i++) rk[0][q[i].id]=i;
	for(int i=1;i<=m[0];i++) a[0][i].l=rk[0][2*i-1],a[0][i].r=rk[0][2*i],in[0][a[0][i].l]=i,out[0][a[0][i].r]=i;
	for(int i=1;i<=m[1];i++) q[2*i-1].k=read(),q[2*i-1].id=2*i-1,q[2*i].k=read(),q[2*i].id=2*i;
	sort(q+1,q+2*m[1]+1);
	for(int i=1;i<=2*m[1];i++) rk[1][q[i].id]=i;
	for(int i=1;i<=m[1];i++) a[1][i].l=rk[1][2*i-1],a[1][i].r=rk[1][2*i],in[1][a[1][i].l]=i,out[1][a[1][i].r]=i;
	solve(0),solve(1);
	for(int i=0;i<=n;i++) ANS=max(ANS,ans[0][i]+ans[1][n-i]);
	printf("%lld\n",ANS);
	return 0;
}

2.括号序列(bracket)

//此题解来自于QAQQWQ

初始:O(n4)

看到题面,容易想到是区间 dp。设 dp_{i,j}dpi,j​ 为将 i 到 j 的位置中所有问号填满,使其成为符合规范的超级括号序列的方案种数,考虑 dp_{i,j}dpi,j​ 可以由那些情况转移而来。

  • i 与 j 可能是匹配的,那么一共有三种转移。将 i+1 到 j-1 的位置作为 S,由 dp_{i+1,j-1}dpi+1,j−1​ 转移,以及将 i+1 到 j-1 的位置为 SA 与 AS 类型的串。
  • i 到 j 为 ASB 类型的种数。

开始 O(n))预处理一次可能成为 * 的长度可以做到 O(1) 判断一段串是否可能全为 * 。

但是写完代码后发现第二个样例无法通过,会算重。考虑这样一种情况 ()()() 它会在两个分割点被计算两次。那么我们可以限制它在一个方向上合并,这样做后答案是正确的,复杂度 O(n^4) 可以得到 40 分

O(n3)

加上前缀和可以优化到 O(n^3) 可以通过本题。

//其实就是前缀和压维。。。

#include
using namespace std;
int n,k;
const int mod=1e9+7;
long long dp[509][509],dps[509][509],sum[509][509];
int xh[509];
char s[509];
int read(){
	int sm=0,d=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-'){
			d=-1;
		}
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		sm*=10;
		sm+=c-'0';
		c=getchar();
	}
	return sm*d;
}
bool check(int l,int r){
	return (xh[r]>=r-l+1&&r-l+1<=k);
}
int main(){
	n=read();
	k=read();
	cin>>s+1;
	for(int i=1;i<=n;i++){
		if(s[i]=='?'||s[i]=='*'){
			xh[i]=xh[i-1]+1;
		}
		else xh[i]=0;
	}
	for(int j=1;j<=n;j++){
		for(int i=j-1;i;i--){
			if((s[i]=='('||s[i]=='?')&&(s[j]==')'||s[j]=='?')){
				if(check(i+1,j-1)){
					dp[i][j]=(dp[i][j]+1)%mod;
				}
				dp[i][j]=(dp[i][j]+dps[i+1][j-1])%mod;
				for(int mid=i+1;mid

番外:

考试的时候我一道题差点忘打freopen。。。

你可能感兴趣的:(贪心算法,算法)