T1 廊桥分配
题意:给两组线段,分别 m 1 m1 m1 、 m 2 m2 m2 个,共计 n n n 条直线,要求每组内线段起点靠前的必须优先放置,同一条直线上的线段互不相交,问如何分配这 n n n 条直线使线段放置的总数最多
首先 O O O( n 2 l o g n n^2logn n2logn)的做法很显然,枚举分配给 x x x 组 1 − n 1-n 1−n 个廊桥, y y y 组相应的用 n n n 减,然后模拟飞机进廊桥算总和取 m a x max max 即可
考虑怎么优化
挖掘题目性质:根据“先到先得”的原则,只要来了一个飞机,只要还有空余的廊桥,就必须把它放进去,所以当这一组廊桥数目增多时,原来在哪个廊桥的飞机仍然在哪个廊桥。
(重点部分:)所以,如果强制让它进入编号最小的廊桥,这样不会影响进入廊桥飞机的集合。这样的话,我们在每个编号为 i i i 的廊桥上记录可以放多少个飞机 c c c[ i i i] ,那么给该组 a a a 个廊桥时,放的飞机总数就是 c c c[ 1 1 1] 到 c c c[ a a a] 的和,即前缀和。这样预处理,我们成功将外层枚举分配廊桥个数的复杂度降下来了。现在复杂度为 O O O( n l o g n nlogn nlogn)
最后说一下用小根堆来模拟飞机进廊桥的过程,开一个堆 s s s ,表示现在可用的廊桥编号,堆 q q q 表示在廊桥内的飞机的结束时间和其所在的廊桥编号(用个 p a i r pair pair 打包即可)。具体实现:我们钦定来一个飞机先进入编号小的廊桥,对于一个新来的飞机 x x x ,先把所有已占廊桥中的飞机的结束时刻比 x x x 到达时刻小的 p o p pop pop 掉,然后若还有剩余廊桥,就把 x x x 的结束时刻捆上当前空余廊桥的最小编号扔进堆里。
#include
using namespace std;
int n,m1,m2,ans,cx[100005],cy[100005];
struct node{int s,t;}x[100005],y[100005];
bool cmp(node a,node b){return a.s<b.s;}
priority_queue<int,vector<int>,greater<int> >s;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
int main(){
scanf("%d%d%d",&n,&m1,&m2);
for(int i=1;i<=m1;i++)scanf("%d%d",&x[i].s,&x[i].t);
for(int i=1;i<=m2;i++)scanf("%d%d",&y[i].s,&y[i].t);
sort(x+1,x+1+m1,cmp);sort(y+1,y+1+m2,cmp);
for(int i=1;i<=n;i++)s.push(i);
for(int i=1;i<=m1;i++){
if(!q.empty()){
while(!q.empty()&&x[i].s>q.top().first)
s.push(q.top().second),q.pop();
}
if(!s.empty()){
int k=s.top();s.pop();
cx[k]++;q.push(make_pair(x[i].t,k));
}
}
for(int i=1;i<=n;i++)cx[i]+=cx[i-1];
while(!s.empty())s.pop();while(!q.empty())q.pop();
for(int i=1;i<=n;i++)s.push(i);
for(int i=1;i<=m2;i++){
if(!q.empty()){
while(!q.empty()&&y[i].s>q.top().first)
s.push(q.top().second),q.pop();
}
if(!s.empty()){
int k=s.top();s.pop();
cy[k]++;q.push(make_pair(y[i].t,k));
}
}
for(int i=1;i<=n;i++)cy[i]+=cy[i-1];
for(int i=0;i<=n;i++)ans=max(ans,cx[i]+cy[n-i]);
printf("%d\n",ans);
return 0;
}
T2 括号序列
根据题目描述,符合要求的括号序列可以分为两类
1.包含型:(),(A),(S),(AS),(SA)
2.并列型:AA,ASA
其中 S 表示任意一个仅由不超过k 个字符 * 组成的非空字符串
区间dp,设dp[i,j,k]表示从位置i到位置j一共合法序列的总情况数,k表示不同的形态种类
(...)**(...)***
的括号序列(即左边以括号序列开头,右边以*结尾)(...)***(...)*(...)
的括号序列(即左边以括号序列开头,右边以括号序列结尾,注:第2种形态也属于这种形态)***(...)**(...)
的括号序列(即左边以*开头,右边以括号序列结尾)***(...)**(...)**
的括号序列(即左边以*
开头,右边以*
结尾,注:第一种形态也属于这种形态)#include
#define int long long
using namespace std;
const int mod=1e9+7;
int n,k,dp[505][505][6];
char s[505];
bool check(int a,int b){return (s[a]=='('||s[a]=='?')&&(s[b]==')'||s[b]=='?');}
signed main(){
scanf("%lld%lld",&n,&k);scanf("%s",s+1);
for(int i=1;i<=n;i++)dp[i][i-1][0]=1;
for(int len=1;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
if(len<=k)dp[l][r][0]=dp[l][r-1][0]&&(s[r]=='*'||s[r]=='?');
if(len>=2){
if(check(l,r)) dp[l][r][1]=(dp[l+1][r-1][0]+dp[l+1][r-1][2]+dp[l+1][r-1][3]+dp[l+1][r-1][4])%mod;
for(int i=l;i<=r-1;i++){
dp[l][r][2]=(dp[l][r][2]+dp[l][i][3]*dp[i+1][r][0])%mod;
dp[l][r][3]=(dp[l][r][3]+(dp[l][i][2]+dp[l][i][3])*dp[i+1][r][1])%mod;
dp[l][r][4]=(dp[l][r][4]+(dp[l][i][4]+dp[l][i][5])*dp[i+1][r][1])%mod;
dp[l][r][5]=(dp[l][r][5]+dp[l][i][4]*dp[i+1][r][0])%mod;
}
}
dp[l][r][5]=(dp[l][r][5]+dp[l][r][0])%mod;
dp[l][r][3]=(dp[l][r][3]+dp[l][r][1])%mod;
}
}
printf("%lld\n",dp[1][n][3]);
}
T3 回文
题意:给定一个正整数序列 a a a 和一个空序列 b b b ,每次可以从 a a a 的左端或右端取出一个正整数放入 b b b 的左端,并分别记为 L L L 和 R R R ,求使 b b b 成为回文串且操作序列字典序最小的操作方案。
首先最开始一定取 a 1 a_1 a1 或 a n a_n an ,以取 a 1 a_1 a1 为例:找到与 a 1 a_1 a1 相同的唯一一个位置记作 p p p ,则序列被划分为两部分,可以看作两个栈, S 1 S_1 S1 : a [ 2... p − 1 ] a[2...p-1] a[2...p−1] 的栈顶是 2 2 2,栈底是 p − 1 p-1 p−1, S 2 S_2 S2: a [ p + 1..2 n ] a[p+1..2n] a[p+1..2n] 的栈顶为 2 n 2n 2n,栈底为 p + 1 p+1 p+1,则问题转化为每次可以取走这两个栈之一的栈顶,令最终的到的串是回文串
考虑最终得到的是一个回文串,可知第 i i i 次和第 2 n − i + 1 2n-i+1 2n−i+1 次取出的数应相同,所以可以同时分析并处理第 i i i 次与第 2 n − i + 1 2n-i+1 2n−i+1 次操作,即需要同时处理两部分中当前最先取出与最后被处理的数(可以在同一部分取,也可以在不同部分取),也就是两部分分别的两端。
考虑用双端队列存储这两个部分,从出序列方向的正向出队列代表第 i i i 次操作,从出序列方向的反向出队列代表第 2 n − i + 1 2n-i+1 2n−i+1 次操作。第 i i i 次操作按照优先级有如下 4 种情况:
则对于每次操作我们只需按照优先级进行判断,优先执行高优先级的操作,不断重复,如果有哪次操作一种都执行不了,则输出-1,否则一直执行到结束位置,最终输出生成的操作序列即可,复杂度 O ( n ) O(n) O(n)
#include
using namespace std;
int t,n,a[1000005];
char s[1000005];
inline bool work(int l1,int r1,int l2,int r2){
for(int i=1;i<=n-1;i++){
if(l1<=r1&&((l2<=r2&&a[l1]==a[l2])||(l1<r1&&a[l1]==a[r1]))){
if(l1<r1&&a[l1]==a[r1]){
l1++;r1--;s[i]='L';s[2*(n-1)-i+1]='L';
}else{
l1++;l2++;s[i]='L';s[2*(n-1)-i+1]='R';
}
}
else if(l2<=r2&&((l1<=r1&&a[r2]==a[r1])||(l2<r2&&a[l2]==a[r2]))){
if(l2<r2&&a[l2]==a[r2]){
l2++;r2--;s[i]='R';s[2*(n-1)-i+1]='R';
}
else {
r2--;r1--;s[i]='R';s[2*(n-1)-i+1]='L';
}
}
else return 0;
}
return 1;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);int p1=-1,p2=-1;
for(int i=1;i<=n*2;i++)scanf("%d",&a[i]);
for(int i=1;i<=n*2+1;i++)s[i]=0;
for(int i=2;i<=n*2;i++){if(a[1]==a[i]){p1=i;break;}}
for(int i=1;i<=n*2-1;i++){if(a[2*n]==a[i]){p2=i;break;}}
if(work(2,p1-1,p1+1,n*2)){printf("L%sL\n",s+1);}
else if(work(1,p2-1,p2+1,n*2-1)){printf("R%sL\n",s+1);}
else printf("-1\n");
}
return 0;
}