2022暑期hdu3

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);
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值