2 Boss Rush
题解
二分答案,转化为判断 T 帧内能否打败 BOSS,即求出 T 帧内能打出的最高伤害,判断是 否大于等于 H。
从前往后依次发动若干个技能,则下一个技能可以发动的时刻等于之前发动过的技能的演 出时间之和,因此只和之前发动过哪些技能有关。设 fS 表示发动了 S 集合的技能,在 T 帧内 最多能结算多少伤害,枚举不在 S 中的某个技能 x 作为下一个技能进行转移,由于技能发动时 刻已知,因此可以 O(1) 计算出在 T 帧内下一个技能可以结算多少伤害。
时间复杂度 O(n2 n log ans)。
标程
#include<cstdio>
typedef long long ll;
const int N=18,M=100005;
int Case,n,i,j,S,t[N],d[N],l,r,ans,mid,sum[(1<<N)+1];
ll hp,f[(1<<N)+1],dmg[N][M];
inline void up(ll&a,ll b){a<b?(a=b):0;}
bool check(int T){
int S,i;
for(S=0;S<1<<n;S++)f[S]=-1;
f[0]=0;
for(S=0;S<1<<n;S++){
ll w=f[S];
if(w<0)continue;
if(w>=hp)return 1;
int cur=sum[S];
if(cur>T)continue;
for(i=0;i<n;i++)if(!(S>>i&1)){
if(cur+d[i]-1<=T)up(f[S|(1<<i)],w+dmg[i][d[i]-1]);
else up(f[S|(1<<i)],w+dmg[i][T-cur]);
}
}
return 0;
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%lld",&n,&hp);
ans=-1,l=r=0;
for(i=0;i<n;i++){
scanf("%d%d",&t[i],&d[i]);
r+=t[i]+d[i]-1;
for(j=0;j<d[i];j++)scanf("%lld",&dmg[i][j]);
for(j=1;j<d[i];j++)dmg[i][j]+=dmg[i][j-1];
}
for(S=1;S<1<<n;S++)sum[S]=sum[S-(S&-S)]+t[__builtin_ctz(S&-S)];
while(l<=r){
mid=(l+r)>>1;
if(check(mid))r=(ans=mid)-1;else l=mid+1;
}
printf("%d\n",ans);
}
}
3 Cyber Language
题解
签到模拟,遍历每个字符,如果一个字符是小写字母且前一个字符是空格或者它是第一个 字符,那么把它转大写输出。
标程
#include<cstdio>
#include<cstring>
const int N=1000005;
int Case,n,i;char s[N];
int main(){
fgets(s,N,stdin);
sscanf(s,"%d",&Case);
while(Case--){
fgets(s,N,stdin);
n=strlen(s);
for(i=0;i<n;i++)
if(s[i]>='a'&&s[i]<='z')
if(!i||(i>0&&s[i-1]==' '))
putchar(s[i]-'a'+'A');
puts("");
}
}
8 Laser Alarm
题解
三个不共线的点可以确定一个平面。对于任意一个平面,将其调整至经过三个顶点,结果 不会变差。因此枚举三个顶点得到平面,然后 O(n) 计算触碰了该平面的线段数,更新答案即 可。所有点都共线的情况需要特判。 时间复杂度 O(n^4 )。
标程
#include<cstdio>
const int N=55;
int Case,n,i,j,k,o,now,ans;
struct P{
int x,y,z;
P(){}
P(int _x,int _y,int _z){x=_x,y=_y,z=_z;}
P operator-(const P&p)const{return P(x-p.x,y-p.y,z-p.z);}
P operator*(const P&p)const{return P(y*p.z-z*p.y,z*p.x-x*p.z,x*p.y-y*p.x);}
int operator^(const P&p)const{return x*p.x+y*p.y+z*p.z;}
bool operator==(const P&p)const{return x==p.x&&y==p.y&&z==p.z;}
}p[N*2];
inline int ptoplane(const P&a,const P&b,const P&c,const P&p){return((b-a)*(c-a))^(p-a);}
inline bool colinear(const P&a,const P&b,const P&p){
P t=(a-b)*(b-p);
return !t.x&&!t.y&&!t.z;
}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d",&n);
for(i=0;i<n+n;i++)scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
ans=1;
for(i=0;i<n+n;i++)for(j=0;j<i;j++){
if(p[i]==p[j])continue;
for(k=0;k<j;k++){
if(p[i]==p[k])continue;
if(p[j]==p[k])continue;
if(colinear(p[i],p[j],p[k]))continue;
now=0;
for(o=0;o<n;o++){
int x=ptoplane(p[i],p[j],p[k],p[o<<1]);
int y=ptoplane(p[i],p[j],p[k],p[o<<1|1]);
if(!x||!y||(x<0&&y>0)||(x>0&&y<0))now++;
}
if(now>ans)ans=now;
}
now=0;
for(o=0;o<n;o++)
if(colinear(p[i],p[j],p[o<<1])||colinear(p[i],p[j],p[o<<1|1]))
now++;
if(now>ans)ans=now;
}
printf("%d\n",ans);
}
}
9 Package Delivery
题解
考虑 r 最小的那个区间 k,第一次取快递放在第 rk 天一定不会使结果变差。此时可能有很 多区间覆盖了 rk,那么为了尽量延后下一次取快递的日期,此时的最优策略应该是选择覆盖 rk且 r 值最小的 k 个区间,使用堆找到并去掉这些区间后,问题就递归了。重复上述过程直至处 理完所有 n 个区间。 时间复杂度 O(n log n)。
标程
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
typedef pair<int,int>P;
const int N=100005;
int Case,n,k,i,j,t,ans,ql[N],qr[N],del[N];
P e[N];
priority_queue<P,vector<P>,greater<P> >q;
inline bool cmpl(int x,int y){return e[x].first<e[y].first;}
inline bool cmpr(int x,int y){return e[x].second<e[y].second;}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%d",&n,&k);
for(i=1;i<=n;i++){
scanf("%d%d",&e[i].first,&e[i].second);
ql[i]=i;
qr[i]=i;
del[i]=0;
}
sort(ql+1,ql+n+1,cmpl);
sort(qr+1,qr+n+1,cmpr);
for(ans=0,i=j=1;i<=n;i++){
if(del[qr[i]])continue;
while(j<=n&&e[ql[j]].first<=e[qr[i]].second){
q.push(P(e[ql[j]].second,ql[j]));
j++;
}
ans++;
for(t=1;t<=k;t++){
if(q.empty())break;
del[q.top().second]=1;
q.pop();
}
}
printf("%d\n",ans);
}
}
10 Range Reachability Query
题解
离线询问,设 fi,j 表示 i 点能否仅通过编号在 [lj , rj ] 之间的边走到第 j 个询问的目的地 vj,共 O(nq) 个 01 状态,可以使用 bitset 存储。第 j 个询问的答案即为 f_{u_j ,j}。
考虑一条边 u → v 的转移,假设它的编号为 k,令所有满足 l ≤ k ≤ r 的询问的集合为 S, 则有 f[u] |= f[v] & S。接下来考虑如何得到集合 S,一个直接的想法是从 1 到 m 依次考虑 每条边,维护覆盖当前边的询问集合,每个询问拆成两个事件:
- 在 l 处加入集合。
- 在 r + 1 处离开集合。
上述方法需要保存 m 个 bitset,空间复杂度过高。节省空间的方法是每 O( √q) 个事件保 存一次 bitset,这样只需保存 O( √q) 个 bitset,然后每次要得到集合 S 时,再往对应 bitset 的 副本中暴力模拟 O( √q) 个事件。
总时间复杂度 O( mq/w + m √q)。
标程
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
const int N=50005,M=100005,Q=50005,K=785,B=325;
int Case,n,m,q,all,ce,i,j,k,x,y,l,r,g[N],v[M],nxt[M],que[Q][2],en[M];
ull f[N][K],h[Q*2/B+3][K],cur[K];
struct E{int x,y;E(){}E(int _x,int _y){x=_x,y=_y;}}e[Q*2];
inline bool cmp(const E&a,const E&b){return a.x<b.x;}
inline void flip(ull*f,int x){f[x>>6]^=1ULL<<(x&63);}
inline void clr(ull*f){for(int i=0;i<=all;i++)f[i]=0;}
inline void copy(ull*f,ull*g){for(int i=0;i<=all;i++)f[i]=g[i];}
inline void trans(ull*f,ull*g,ull*h){for(int i=0;i<=all;i++)f[i]|=g[i]&h[i];}
int main(){
scanf("%d",&Case);
while(Case--){
scanf("%d%d%d",&n,&m,&q);
for(i=1;i<=m;i++){
scanf("%d%d",&x,&y);
v[i]=y;
nxt[i]=g[x];
g[x]=i;
}
all=(q-1)>>6;
for(i=1;i<=n;i++)clr(f[i]);
for(i=0;i<q;i++){
scanf("%d%d%d%d",&x,&y,&l,&r);
que[i][0]=x;
que[i][1]=y;
flip(f[y],i);
e[++ce]=E(l,i);
e[++ce]=E(r+1,i);
}
sort(e+1,e+ce+1,cmp);
for(i=1,j=0;i<=m;i++){
while(j<ce&&e[j+1].x<=i)j++;
en[i]=j;
}
clr(cur);
for(i=1;i<=ce;i++){
flip(cur,e[i].y);
if(i%B==0)copy(h[i/B],cur);
}
for(i=n;i;i--)for(j=g[i];j;j=nxt[j]){
x=en[j];
copy(cur,h[x/B]);
for(k=x/B*B+1;k<=x;k++)flip(cur,e[k].y);
trans(f[i],f[v[j]],cur);
}
for(i=0;i<q;i++)puts(f[que[i][0]][i>>6]>>(i&63)&1?"YES":"NO");
for(i=1;i<=n;i++)g[i]=0;
ce=0;
}
}
12 Two Permutations
题解
首先特判序列 S 中每个数字出现次数不都为 2 的情况,此时答案为 0。 动态规划,设 fi,j 表示 P 的前 i 项匹配上了 S,且 Pi 匹配 S 中数字 Pi 第 j 次出现的位 置时,有多少种合法的方案。由于 S 中每个数字出现次数都为 2,因此状态数为 O(n)。转移时 枚举 Pi+1 匹配哪个位置,那么 Pi 匹配的位置与 Pi+1 匹配的位置中间的那段连续子串需要完 全匹配 Q 中对应的子串,使用字符串 Hash 进行 O(1) 判断即可。 时间复杂度 O(n)。
标程
#include<cstdio>
typedef unsigned long long ull;
const int N=300005,P=998244353,S=233;
int Case,n,i,j,k,x,y;
int a[N],b[N],c[N*2],pc[N][2],f[N][2],ans;
ull p[N*2],fb[N],fc[N*2];
inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;}
inline ull ask(ull*f,int l,int r){return f[r]-f[l-1]*p[r-l+1];}
inline bool check(int bl,int br,int cl,int cr){
if(bl>br)return 1;
if(bl<1||br>n||cl<1||cr>n+n)return 0;
return ask(fb,bl,br)==ask(fc,cl,cr);
}
int main(){
for(p[0]=i=1;i<N*2;i++)p[i]=p[i-1]*S;
scanf("%d",&Case);
while(Case--){
scanf("%d",&n);
for(i=1;i<=n;i++)pc[i][0]=pc[i][1]=0;
for(i=1;i<=n;i++)scanf("%d",&a[i]);
for(i=1;i<=n;i++)scanf("%d",&b[i]),fb[i]=fb[i-1]*S+b[i];
for(i=1;i<=n+n;i++){
scanf("%d",&x);
c[i]=x;
fc[i]=fc[i-1]*S+x;
if(!pc[x][0])pc[x][0]=i;else pc[x][1]=i;
}
for(i=1;i<=n;i++)if(!pc[i][0]||!pc[i][1])break;
if(i<=n){
puts("0");
continue;
}
for(i=1;i<=n;i++)for(j=0;j<2;j++)f[i][j]=0;
for(j=0;j<2;j++){
x=pc[a[1]][j];
if(check(1,x-1,1,x-1))f[1][j]=1;
}
for(i=1;i<n;i++)for(j=0;j<2;j++)if(f[i][j]){
x=pc[a[i]][j];
for(k=0;k<2;k++){
y=pc[a[i+1]][k];
if(y<=x)continue;
if(check(x-i+1,y-i-1,x+1,y-1))up(f[i+1][k],f[i][j]);
}
}
ans=0;
for(j=0;j<2;j++)if(f[n][j]){
x=pc[a[n]][j];
if(check(x-n+1,n,x+1,n+n))up(ans,f[n][j]);
}
printf("%d\n",ans);
}
}