题单

本文章正在持续更新……

【2021】 廊桥分配


题目

题面描述

所有飞机分为两类——国内区和国际区,两区廊桥数量互不干扰。每架飞机遵循“先到先得”的原则,优先选择编号最小的廊桥,如果该区的廊桥满了,便前往远机区(假设远机区数量无限)。

求一种分类方案(将廊桥分给国内区和国际区),使得能停靠在廊桥旁飞机的数量最多。

题解

不难想到枚举国内区和国际区各自的数量,然后每次都进行一次模拟,用优先队列维护即可。

这样你可以获得优秀的 45pts,好好卡一下可以卡到 60pts。

然后再思考正解,先观察一下样例解释:

也就是,如果只有 \(x\) 个廊桥的时候第 \(i\) 架飞机可以停在廊桥旁,那么之后所有大于 \(x\) 个廊桥的方案数,第 \(i\) 架飞机都可以被满足。

或者我们只考虑国内区,假设有无限个廊桥(其实有多少架飞机,就多少个廊桥就行),这样每个飞机都可以停靠在一个廊桥边,设 \(sum_{[i]}\) 表示有多少架飞机停在编号为 \(i\) 的廊桥边。现在廊桥数突然减少到了 \(x\) 个,那么原本停靠在编号在 \(x\) 之后的飞机现在一定都没廊桥可以停了。那么现在可以停在廊桥边上飞机的数量,就是 \(\sum\limits_{i=1}^x sum_{[i]}\)。

这样预处理一遍,查询枚举后用前缀和即可。

优先队列时间复杂度是 \(O(\log n)\),总共最多有 \(m_1+m_2\) 次队列操作,故总时间复杂度为 \(O((m_1+m_2)\log n)\)。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,gn,gw;
int suma[N],sumb[N]; #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline void read(T& r) {
r=0;bool w=0; char ch=getchar();
while(ch<'0'||ch>'9') w=ch=='-'?1:0,ch=getchar();
while(ch>='0'&&ch<='9') r=r*10+(ch^48), ch=getchar();
r=w?-r:r;
} struct node{
int st,en;
}pn[N],pw[N]; struct Node{
int pos,x;
bool operator < (const Node &a) const{
return this->x>a.x;
}
}; bool cmp(node x,node y){return x.st<y.st;} int main(){
// freopen("airport3.in","r",stdin);
ios::sync_with_stdio(false);
read(n),read(gn),read(gw);
priority_queue<Node>q,q1;
for(int i=1;i<=gn;i++){
read(pn[i].st),read(pn[i].en);
Node nxt={0,i};
q.push(nxt);
}
//处理国内航班
sort(pn+1,pn+gn+1,cmp);//首先按抵达时间排序
for(int i=1;i<=gn;i++){
while(q1.empty()==false&&q1.top().x<=pn[i].st){//如果飞机飞走了
Node nxt={0,q1.top().pos};
q1.pop();
q.push(nxt);
}
int x=q.top().x;
q.pop();
suma[x]++;
Node nxt={x,pn[i].en};
q1.push(nxt);
}
//清空队列们
while(q.empty()==false) q.pop();
while(q1.empty()==false) q1.pop();
//处理国际航班
for(int i=1;i<=gw;i++){
read(pw[i].st),read(pw[i].en);
Node nxt={0,i};
q.push(nxt);
}
sort(pw+1,pw+gw+1,cmp);
for(int i=1;i<=gw;i++){
while(q1.empty()==false&&q1.top().x<=pw[i].st){
Node nxt={0,q1.top().pos};
q1.pop();
q.push(nxt);
}
int x=q.top().x;
q.pop();
sumb[x]++;
Node nxt={x,pw[i].en};
q1.push(nxt);
}
//处理前缀和
for(int i=1;i<=n;i++) sumb[i]+=sumb[i-1];
for(int i=1;i<=n;i++) suma[i]+=suma[i-1];
//枚举答案
int maxi=0;
for(int x=0;x<=n;x++){
int y=n-x;
maxi=max(suma[x]+sumb[y],maxi);
}
cout<<maxi;
return 0;
}

【2021】 括号序列

题目

题面描述

给出一个长度为 \(n\) 的序列,其中有一些位置的字符已经确定,另外一些位置的字符尚未确定(用 ? 表示),询问有多少种将所有尚未确定的字确定的方法,使得得到的字符串是一个符合规范的序列?

如果一个序列符合规范,则它需要满足以下条件:

  1. ()(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 \(\bm{k}\) 字符 * 组成的非空字符串(以下两条规则中的 S 均为此含义);
  2. 如果字符串 AB 均为符合规范的超级括号序列,那么字符串 ABASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)(SA)(AS) 均为符合规范的超级括号序列。
  4. 所有符合规范的超级括号序列均可通过上述 3 条规则得到。

题解

观察序列的情况,整理可得有以下几种情况:

  1. 序列左右两边都是 *,如 ***********()***
  2. 序列一边是 *,一边是(),如 (**)*****(****)
  3. 序列左右两边都是 () ,如 (**)*(***)(**(**)**(***))

不难发现,只有第三种情况符合条件,而第三种情况细分可以得到以下几种情况:

  1. (******),由第一种情况加上括号得到。
  2. (***)(*)(***)**(*),由第三种情况和第二种情况中 * 在左边得到。
  3. (***(*)),由第二种情况加上括号得到。

这样这三种情况显然不够用,我们需要再加几种情况判断。新得到的情况由以下几种:

  1. 全部都是 *
  2. 左右两个括号相匹配,包含里面所有字符。
  3. 左边是 *,右边是括号序列。
  4. 左右两边都是括号,特殊地,第一种情况也包含在内。
  5. 左边是括号序列,右边是 *
  6. 左右两边都是 *,特殊地,第零种情况也包含在内。

于是我们可以设计一个三维状态 \(dp_{[i][j][6]}\)。

\(dp_{[i][j][0]}\):直接特判。

\(dp_{[i][j][1]}=\begin{cases}
0 & i,j\text{ 括号不匹配}\\
dp_{[i+1][j-1][0]+dp[i+1][j-1][2]+dp[i+1][j-1][3]+dp[i+1][j-1][4]} & i,j\text{ 括号匹配}
\end{cases}\)

注:没有情况 5 是因为两边都是 * 且包含括号序列的是不能在左右两边加上括号的。

\(dp_{[i][j][2]}=\sum\limits_{k=i}^{j-1}(dp_{[i][k][2]}+dp_{[i][k][5]})\times dp_{[k+1][j][1]}\)

注:这里也就是要求左边以 * 开头且结尾不限,右边以括号结尾。

\(dp_{[i][j][3]}=\sum\limits_{k=i}^{j-1}(dp_{[i][k][3]}+dp_{[i][k][4]})\times dp_{[k+1][j][1]}+dp_{[i][j][1]}\)

注:这里要求左边以括号开头且结尾不限,右边以全 * 序列结尾。

\(dp_{[i][j][4]}=\sum\limits_{k=i}^{j-1}dp_{[i][k][3]}\times dp_{[k+1][j][0]}\)

注:这里要求左边以括号开头且结尾也是括号,右边以全 *序列 结尾。

\(dp_{[i][j][5]}=\sum\limits_{k=i}^{j-1}(dp_{[i][k][2]} \times dp_{[k+1][j][0]})+dp_{[i][j][0]}\)

注:这里要求左边以 * 开头且结尾为括号,右边以全 * 结尾。

初始状态: \(dp[i][i-1][0]=1\)

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+5;
const int mod=1e9+7;
int n,k;
int a[N];
int dp[N][N][10]; signed main(){
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=n;i++){
char c;
cin>>c;
if(c=='(') a[i]=-1;
else if(c=='*') a[i]=0;
else if(c==')') a[i]=1;
else a[i]=2;
dp[i][i-1][0]=1;
}
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
if(len<=k){
if(a[j]==0||a[j]==2) dp[i][j][0]=dp[i][j-1][0];
}
if(len>=2){
if((a[i]==-1||a[i]==2)&&(a[j]==1||a[j]==2)) dp[i][j][1]=(dp[i+1][j-1][0]+dp[i+1][j-1][2]+dp[i+1][j-1][3]+dp[i+1][j-1][4])%mod;
for(int k=i;k<j;k++){
dp[i][j][2]=(dp[i][j][2]+(dp[i][k][2]+dp[i][k][5])*dp[k+1][j][1])%mod;
dp[i][j][3]=(dp[i][j][3]+(dp[i][k][4]+dp[i][k][3])*dp[k+1][j][1])%mod;
dp[i][j][4]=(dp[i][j][4]+dp[i][k][3]*dp[k+1][j][0])%mod;
dp[i][j][5]=(dp[i][j][5]+dp[i][k][2]*dp[k+1][j][0])%mod;
}
}
dp[i][j][3]=(dp[i][j][3]+dp[i][j][1])%mod;
dp[i][j][5]=(dp[i][j][5]+dp[i][j][0])%mod;
}
}
cout<<dp[1][n][3];
return 0;
}

【2021】 回文

题目

题面描述

给定序列 \(a_1, a_2, \ldots, a_{2n}\),在这 \(2n\) 个数中,\(1, 2, \ldots, n\) 分别各出现恰好 \(2\) 次。进行 \(2 n\) 次操作,创建一个长度为 \(2n\) 序列 \(b\),使得 \(b\) 是一个回文数列,判断是否可行,如果可行便输出字典序最小的方案。操作如下:

  1. 将序列 \(a\) 的开头元素加到 \(b\) 的末尾,并从 \(a\) 中移除。
  2. 将序列 \(a\) 的末尾元素加到 \(b\) 的末尾,并从 \(a\) 中移除。

题解

\(\tiny\text{据说这题竟比T2水}\)。

显然可以贪心的选择 L,如果不行再选择 R,于是可以得到一个 \(40pts\) 的暴力。

至于正解,我们可以先分类讨论第一次怎样取,这里只解说左边的情况。

这样 \(b_{[1]}=a_{[1]}\),那么假设 \(a\) 数组中 \(a_{[x]}=a_{[1]}\) 的话,\(b_{[2n]}=a_{x}\)。也就是 \(a_{[x]}\)一定是 \(a\) 数组中最后一个取出的元素。

那么无论你是怎样取的,\(a_{[x-1]}\) 肯定是比 \(a_{[1]},a_{[2]}...a_{[x-2]}\) 更晚被取到的。同理,\(a_{[x+1]}\) 也会比 \(a_{[x+2]},a_{[x+3]}...a_{[n]}\) 更晚取到。

稍微解释一下,由于 \(a_{[x]}\) 在其他元素被取完时一定是固定不动的,所以它可以将整个序列分为两部分,左半部分是不能从右边取的(因为 \(a_{[x]}\) 还在那呢),同理右半部分也不能从左边取。这样整个序列就被分成了两个栈。

借助图片,我们分析具体的实现:

(此时 \(n=5,A_{[1]}=A_{[6]}\),定义 \(ansi[i]\) 为第 \(i\) 次操作的情况)

这时候如果拿出 \(A_{[2]}\),现在的情况可以分为三种情况:

  1. 如果 \(A_{[7]}=A_{[2]}\) 的话,代表第 \(9\) 次操作一定是取出 \(A_{[7]}\),于是 \(ansi_{[2]}=L,ansi_{[9]}=R\)。

  2. 如果 \(A_{[5]}=A_{[2]}\) 的话,代表第 \(9\) 次操作一定是取出 \(A_{[5]}\),于是 \(ansi_{[2]}=L,ansi_{[9]}=L\)。

  3. 除上面两种情况外,由于与 \(A_{[2]}\) 相等的数 \(x\) 不在栈底,但第 \(9\) 次操作又不得不是取出 \(x\),那么在 \(x\) 下面的数就一定不能被取出。那么这种情况无解。

再分类讨论拿出右边的情况就可以过了这道题了。

代码

暴力:

点击查看代码
void dfs(int x){//x代表已经填了多少位
if(x==n*2+1){
fin=true;
for(int i=1;i<=n*2;i++){
if(ansi[i]==1) cout<<"L";
else cout<<"R";
}
return;
}
if(x<=n||a[lt]==b[n*2-x+1]){
ansi[x]=1;
b[x]=a[lt++];
dfs(x+1);
lt--;
if(fin==true) return;
}
if(x<=n||a[rt]==b[n*2-x+1]){
ansi[x]=2;
b[x]=a[rt--];
dfs(x+1);
rt++;
}
}

正解:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,a[N];
int ansi[N];//存贮操作的答案,1是L,2是R
deque<int>lt,rt;//虽然分析中是栈,但由于还要弹出栈底元素,故用双端队列更好 void start(){//初始化
lt.clear();
rt.clear();
memset(ansi,0,sizeof(ansi));
} int fin(int l,int r,int x){//找到与x相等的数
for(int i=l;i<=r;i++) if(a[i]==x) return i;
return -1;
} void solve(){
int x=2;
while(lt.empty()==false||rt.empty()==false){//如果还有数
if(rt.empty()==true){//右边的取空了,就只考虑左边的了。下面同理
if(lt.front()!=lt.back()){
ansi[1]=0;
return;
}
ansi[x]=ansi[2*n-x+1]=1;
lt.pop_front();
lt.pop_back();
}
else if(lt.empty()==true){
if(rt.front()!=rt.back()){
ansi[1]=0;
return;
}
ansi[x]=ansi[2*n-x+1]=2;
rt.pop_front();
rt.pop_back();
}
else{//对应四种分类讨论情况
if(lt.size()>=2&&lt.front()==lt.back()){//这里一定要保证至少还有两个数,要不然就是自己与自己相等了
ansi[x]=ansi[2*n-x+1]=1;
lt.pop_front();
lt.pop_back();
}
else if(lt.front()==rt.back()){
ansi[x]=1,ansi[2*n-x+1]=2;
lt.pop_front();
rt.pop_back();
}
else if(rt.size()>=2&&rt.front()==rt.back()){
ansi[x]=ansi[2*n-x+1]=2;
rt.pop_front();
rt.pop_back();
}
else if(lt.back()==rt.front()){
ansi[x]=2,ansi[2*n-x+1]=1;
rt.pop_front();
lt.pop_back();
}
else{
ansi[1]=0;
return;
}
}
x++;
}
} int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
cin>>n;
start();
for(int i=1;i<=n*2;i++) cin>>a[i];
//取L
int pos=fin(2,n*2,a[1]);
ansi[1]=ansi[2*n]=1;
for(int i=2;i<=pos-1;i++) lt.push_back(a[i]);
for(int i=n*2;i>=pos+1;i--) rt.push_back(a[i]);
solve();
if(ansi[1]!=0){//如果第一次取L有解,那么它一定是最优解
for(int i=1;i<=n*2;i++){
if(ansi[i]==1) cout<<"L";
else cout<<"R";
}
cout<<"\n";
continue;
}
//取R
lt.clear();rt.clear();
pos=fin(1,n*2-1,a[n*2]);
ansi[1]=2,ansi[2*n]=1;
for(int i=1;i<=pos-1;i++) lt.push_back(a[i]);
for(int i=n*2-1;i>=pos+1;i--) rt.push_back(a[i]);
solve();
if(ansi[1]==0) cout<<"-1\n";
else{
for(int i=1;i<=n*2;i++){
if(ansi[i]==1) cout<<"L";
else cout<<"R";
}
cout<<"\n";
continue;
}
}
return 0;
}

【做题笔记】CSP-S 往年试题的更多相关文章

  1. C语言程序设计做题笔记之C语言基础知识(下)

    C 语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行 事.并且C是相当灵活的,用于执行计算机程序能完成的 ...

  2. C语言程序设计做题笔记之C语言基础知识(上)

    C语言是一种功能强大.简洁的计算机语言,通过它可以编写程序,指挥计算机完成指定的任务.我们可以利用C语言创建程序(即一组指令),并让计算机依指令行事.并且C是相当灵活的,用于执行计算机程序能完成的几乎 ...

  3. SDOI2017 R1做题笔记

    SDOI2017 R1做题笔记 梦想还是要有的,万一哪天就做完了呢? 也就是说现在还没做完. 哈哈哈我竟然做完了-2019.3.29 20:30

  4. SDOI2014 R1做题笔记

    SDOI2014 R1做题笔记 经过很久很久的时间,shzr又做完了SDOI2014一轮的题目. 但是我不想写做题笔记(

  5. SDOI2016 R1做题笔记

    SDOI2016 R1做题笔记 经过很久很久的时间,shzr终于做完了SDOI2016一轮的题目. 其实没想到竟然是2016年的题目先做完,因为14年的六个题很早就做了四个了,但是后两个有点开不动.. ...

  6. LCT做题笔记

    最近几天打算认真复习LCT,毕竟以前只会板子.正好也可以学点新的用法,这里就用来写做题笔记吧.这个分类比较混乱,主要看感觉,不一定对: 维护森林的LCT 就是最普通,最一般那种的LCT啦.这类题目往往 ...

  7. java做题笔记

    java做题笔记 1. 初始化过程是这样的: 1.首先,初始化父类中的静态成员变量和静态代码块,按照在程序中出现的顺序初始化: 2.然后,初始化子类中的静态成员变量和静态代码块,按照在程序中出现的顺序 ...

  8. SAM 做题笔记(各种技巧,持续更新,SA)

    SAM 感性瞎扯. 这里是 SAM 做题笔记. 本来是在一篇随笔里面,然后 Latex 太多加载不过来就分成了两篇. 标 * 的是推荐一做的题目. trick 是我总结的技巧. I. P3804 [模 ...

  9. PKUWC/SC 做题笔记

    去年不知道干了些啥,什么省选/营题都没做. 现在赶应该还来得及(?) 「PKUWC2018」Minimax Done 2019.12.04 9:38:55 线段树合并船新玩法??? \(O(n^2)\ ...

随机推荐

  1. CVI中调用数据库的几点..

    1.各类开发工具开发的程序调用数据库,有两种方式:ADO和ODBC. 2.以ODBC方式访问SQL数据库,必须在电脑管理工具----ODBC管理器 中配置TCP/IP项(IP1.IP2.IP3.IPA ...

  2. Redis 14 发布订阅

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 概述 Redi ...

  3. MySQL(一)——查看密码与修改

    查看数据库密码,策略与修改 查看数据库密码: RPM安装: # cat /var/log/mysqld.log | grep password 源码安装: # cat /usr/local/mysql ...

  4. 【Homebrew】安装

    /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 官网安 ...

  5. 一 策略模式 来自CBF4LIFE 的设计模式

    刘备要到江东娶老婆了,走之前诸葛亮给赵云(伴郎)三个锦囊妙计,说是按天机拆开解决棘手问题,嘿,还别说,真是解决了大问题,搞到后是周瑜陪了夫人又折兵呀,那咱们先看看这个场景是什么样子的. 先说这个场景中 ...

  6. KingbaseES R3 集群cluster日志切割和清理案例

    案例说明: 对于KingbaseES R3集群的cluster日志默认系统是不做切割和清理的,随着运行时长的增加,日志将增长为一个非常大的文件,占用比较大的磁盘空间,并且在分析问题读取大文件时效率很低 ...

  7. 记一次 Sedona(GeoSpark) 空间计算优化

    项目需求需要空间计算能力,开始选型Sedona(GeoSpark)来完成, 需求需要每一条数据在满足某条件的情况下,去查找某张表进行空间匹配,找到离这个点(point)最近的一条道路(lineStri ...

  8. 阿里云Centos7部署私人CSGO服务器

    大四毕业生,论文和答辩分别以1.8%的重复率和只答不辨的态度双双过关.现在就是在家等着学校发毕业证了.顺带学学驾驶...可是我这么一个喜欢折腾的人,怎么能够让自己接受这么无聊的咸鱼时光呢?因为这个寒假 ...

  9. web字体浮在图像中央

    在做项目的过程中遇到了需要将图像作为背景,将字体显示在图像中央需求. 尝试了两种做法: 第一种方法为设置一个div设置属性为relative固定这个框的位置,将图片铺在div块里. 在div再设一个d ...

  10. WinForm完美实现Cefsharp-v49控件C#与JS交互,并且可加载运行flash

    https://blog.csdn.net/zhao331863874/article/details/117328415