廊桥分配
考虑廊桥个数不会影响飞机停靠,所以我们可以将廊桥的个数限制拿掉来看待这个问题。只需要维护一个廊桥的优先队列,然后让飞机每次选取最小的廊桥停靠,如果廊桥有 \(i\) 个,那么到 \(i+1\) 个廊桥就相当于没有停靠到,但是每个廊桥停靠的飞机数目还是一定的。所以我们不妨直接设廊桥个数无限,到了第几个就是做到第几个的前缀和。然后进行模拟,维护一个等待飞走的飞机的优先队列,维护离去的时刻和占用的廊桥,每次弹出离开的,解放廊桥。所以时间复杂度 \(O(n+m+nlogn)\) 。
#include<bits/stdc++.h>
#define pir pair<int,int>
using namespace std;
const int N=1e5+5;
int n,m1,m2,s1[N],s2[N],maxi;
struct air{
int c,l;//come and leave
}a[N],b[N];
void cal(air *q,int m,int *s){
priority_queue<pir,vector<pir>,greater<pir> > q1;
priority_queue<int,vector<int>,greater<int> > q2;
for(int i=1;i<=n;++i) q2.push(i);
for(int i=1;i<=m;++i){
while(!q1.empty()&&q1.top().first<=q[i].c){
//解放廊桥
q2.push(q1.top().second);
q1.pop();
}
if(q2.empty()) continue;
q1.push(make_pair(q[i].l,q2.top()));
s[q2.top()]++;
q2.pop();
}
for(int i=1;i<=n;++i) s[i]+=s[i-1];
return;
}
bool cmp(air x,air y){
return x.c<y.c;
}
int main(){
scanf("%d %d %d",&n,&m1,&m2);
for(int i=1;i<=m1;++i) scanf("%d %d",&a[i].c,&a[i].l);sort(a+1,a+m1+1,cmp);
for(int i=1;i<=m2;++i) scanf("%d %d",&b[i].c,&b[i].l);sort(b+1,b+m2+1,cmp);
cal(a,m1,s1),cal(b,m2,s2);
for(int i=0;i<=n;++i) maxi=max(maxi,s1[i]+s2[n-i]);
printf("%d",maxi);
return 0;
}
括号序列
不考虑枚举 * 而是用来构成合法状态是突破口,设计好的 dp 状态是关键。
为了不算重,我们设计6种状态
0.全为*型
- \((……)\) 括号包含型
- $ (……)(……)(……)** $
- \((……)**(……)**(……)\)
- \(**(……)**(……)**(……)\)
- \(**(……)**(……)**\)
考虑转移。
0. \(dp_{l,r,0}=dp_{l,r-1,0} \ \&\& (s[r]=='?' \ | \ | \ s[r]=='*')\)。就是继承 \(r-1\)。
1.\(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}\times ((s[l]=='?' \ | \ | \ s[l]=='(' \ )\&\& (s[r]=='?' \ | \ | \ s[r]==')' \ ) \ )\)。就是如果括号可以匹配,就把括号内的全部包进去统计答案。
2.\(dp_{l,r,2}=\sum\limits_{p=l}^{r-1} dp_{l,p,3}\times dp_{p+1,r,0}\)。因为2显然可以由3和0拼接得来。
3.\(dp_{l,r,3}=\sum\limits_{p=l}^{r-1} (dp_{l,p,3}+dp_{l,p,2})\times dp_{p+1,r,1}+ dp_{l,r,1}\)。首先要明确括号之间不一定非要有*,所以是2、3合起来向3转移。因为3包含1,所以要加上这个区间1可能的答案,注意是枚举完了才累加。
4.\(dp_{l,r,4}=\sum\limits_{p=l}^{r-1} (dp_{l,p,4}+dp_{l,p,5})\times dp_{p+1,r,1}\)。同理,不过这个转移也可以是 \(dp_{l,p,0}\times dp_{p+1,r,3}\)。
5.\(dp_{l,r,5}=\sum\limits_{p=l}^{r-1} dp_{l,p,4}\times dp_{p+1,r,0}+dp_{l,r,0}\)。5可由4、0拼接而来,然后0也属于5。
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,k;
char s[505];
long long dp[505][505][7];
int read(){
char ch;int x=0,f=1;
while(!isdigit(ch=getchar())){
if(ch=='-') f=-1;
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch-'0');
ch=getchar();
}
return x*f;
}
int main(){
freopen("bracket.in","r",stdin);
freopen("bracket.out","w",stdout);
n=read(),k=read();
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((s[l]=='?'||s[l]=='(')&&(s[r]=='?'||s[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;
if(len>=2){
for(int p=l;p<r;++p){
dp[l][r][2]=(dp[l][r][2]+dp[l][p][3]*dp[p+1][r][0]%mod)%mod;
dp[l][r][3]=(dp[l][r][3]+(dp[l][p][3]+dp[l][p][2])*dp[p+1][r][1]%mod)%mod;
dp[l][r][4]=(dp[l][r][4]+(dp[l][p][4]+dp[l][p][5])*dp[p+1][r][1]%mod)%mod;
dp[l][r][5]=(dp[l][r][5]+dp[l][p][4]*dp[p+1][r][0]%mod)%mod;
}
}
dp[l][r][5]=(dp[l][r][5]+dp[l][r][0])%mod;
dp[l][r][3]=(dp[l][r][1]+dp[l][r][3])%mod;
}
}
// for(int len=1;len<=n;++len){
// for(int l=1;l+len-1<=n;++l){
// int r=l+len-1;
// printf("%d %d %lld/%lld/%lld/%lld/%lld/%lld ",l,r,dp[l][r][0],dp[l][r][1],dp[l][r][2],dp[l][r][3],dp[l][r][4],dp[l][r][5]);
// }
// printf("\n");
// }
printf("%lld",dp[1][n][3]);
return 0;
}
报数
筛法,注意卡常和范围。
数列
这个题还是比较难的。难点在于想到 \(S\) 的位数和 \(\{a_i\}\)的关系其实是二进制填数,而对于 \(S\) 的每一位都可以通过有多少个 \({a_i}\) 来构成唯一确定的状态。
总而言之,设计状态难。
我的思路历程是想到了 \(S\) 和 \(\{a_i\}\) 之间一一对应的关系,但是没有想到枚举当前位可能有多少个 \(\{a_i\}\)。这启示我以后要抓住题目的性质,持续深入地想问题。dp状态的设计要清晰确定可转移,还要满足子结构。dp的状态就是一个明确的语句,表达的意思就是你对题目答案是如何组成的理解。
设 \(dp_{i,j,s,t}\) 表示当前考虑到 \(S\) 的第 \(i\) 位,\(a\) 中我们已经确定了几个元素(注意,这里确定了几个元素是相对于 \(S\) 枚举到了第几位而言的,每多枚举一位都有可能让其不一样,而我们枚举的只是确定了几个元素,因为序列是无序的,我们不能说到了第 \(j\) 个元素,这是一种相对关系),到这一位为止有多少个 1 (为什么会想到这个呢?首先是因为题目中有1的个数的限制,其次是我们要确定这一个才能确定每一位的值,才能让 \(S\) 维持一个二进制数的性质,不然就成了什么十进制之类的数了,那样显然不利于解决问题,所以这一维对于我们确定当前处于什么阶段和状态的转移是不可缺少的),这一位向下一位进多少个1(这个和上一维同理)。
我们解决了状态的设计,那么转移方程式就清晰可见了。由于我们不好确定\(i-1\)位的状态,所以我们直接向下一位转移:
\[{\large dp_{i+1,j+p,s+(t+p)\%1,\lfloor \frac{(t+p)}{2} \rfloor}=\sum\limits_{p=0}^{n-j} dp_{i,j,s,t}\times v_{a_i}^p\times \binom{n-j}{p}} \]我当前位可能有多个1,所以我要枚举有多少个,所以我就多确定了这么多个。有多少一就是上一位有多少一加上上一位的进位加上这一位新加了多少 \(\%2\),向前进同理。
注意范围和预处理。
注意到了\(m\)位有可能还是多个1,所以不能直接在\(m\)位统计答案,要向后面进位一下才可以。
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m,k;
long long dp[105][35][35][17],C[105][35],pv[105][35],v[105],ans;
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
scanf("%d %d %d",&n,&m,&k);
for(int i=0;i<=m;++i) scanf("%lld",&v[i]);
for(int i=0;i<=m;++i){
pv[i][0]=1;
for(int j=1;j<=n;++j) pv[i][j]=pv[i][j-1]*v[i]%mod;
}
for(int i=0;i<=n;i++) C[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
dp[0][0][0][0]=1;
for(int i=0;i<=m;++i){
for(int j=0;j<=n;++j){
for(int s=0;s<=k;++s){
for(int t=0;t<=n>>1;++t){
for(int p=0;p<=n-j;++p){
dp[i+1][j+p][s+((t+p)&1)][(t+p)>>1]=(dp[i+1][j+p][s+((t+p)&1)][(t+p)>>1]+(dp[i][j][s][t]*pv[i][p]%mod)*C[n-j][p]%mod)%mod;
}
}
}
}
}
for(int s=0;s<=k;++s){
for(int t=0;t<=n>>1;++t){
if(s+__builtin_popcount(t)<=k) ans+=dp[m+1][n][s][t],ans%=mod;
}
}
printf("%lld\n",ans);
return 0;
}
标签:21,int,times,枚举,两题,廊桥,dp,mod
From: https://www.cnblogs.com/mountzhu/p/18458701