2022“杭电杯”中国大学生算法设计超级联赛(3)

目录

1002.Boss Rush(二分+状压dp)

1003.Cyber Language(签到)

1009.Package Delivery

1011.Taxi

1012.Two Permutations


斗奋力努

1002.Boss Rush(二分+状压dp)

题意:有一只血量为h的怪兽.你有n个技能,每个技能有一个冷却时间和持续伤害时间(两者不一定相同),对于每个技能的持续伤害时间,下面有输入.问消灭怪兽需要最短的时间是多少

思路:最短时间想到二分,本题我们二分一个消灭怪兽的最短时间,在check一下是否能消灭.在check里面,因为只有18个技能(最多),想到状压dp.对于f [sta],我们的定义是:sta是一个18位的二进制数,第几位的1表示释放该技能.f [sta]里面存储的是这个技能使用状态下的最高伤害.那么我们的转移方程式就是

f[i]     =   max(f[i] , f[i^(1<<j)]+造成的伤害)
(当前状态)        (当前状态二进制数位上去个0,即为上一层状态)

这里的意思就是我们对于第j位上为1的状态,我们去掉这个1,就是它的上一层的状态,我们只需要由上一层加上使用第j个技能的伤害就可以更新f[i]的状态.

当然,这里造成的伤害也需要进行处理,为了o1的时间读取,直接求一个前缀和即可.对于这里的状压我们可以直接从0开始枚举到2^n-1.因为之后的状态肯定可以从前面转移过来(可以打表参考一下)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =1e5+10,mod=998244353;
int t[20],ti[1<<20],len[20];
int atk[20][N];
int f[1<<20];
int n,h;
int check(int lim)
{    
    int maxx=0;
    f[0]=0;
    for(int i=1;i<(1<<n);i++)
    {
        int sta=i,time=ti[i];
        f[i]=0;
        for(int j=0;j<n;j++)
        {
            if((sta>>j)&1)
            {
                if(lim-(time-t[j+1])+1<=0)
                    continue;
                f[sta]=max(f[sta^(1<<j)]+atk[j+1][min(len[j+1],lim-(time-t[j+1])+1)],f[sta]);     
                maxx=max(maxx,f[sta]);
            }
        }
        if (f[i]>=h) return true;
    }
    return false;
}
void solve()
{
    int l=0,r=1e7;
    scanf("%lld%lld",&n,&h);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&t[i],&len[i]);
        for(int j=1;j<=len[i];j++)
            scanf("%lld",&atk[i][j]);
        for(int j=1;j<=len[i];j++)
            atk[i][j]+=atk[i][j-1];
    }
    for(int i=0;i<(1<<n);i++)
    {
        ti[i]=0;
        for(int j=0;j<n;j++)
            if((i>>j)&1)
                ti[i]+=t[j+1];
    }
    while(l<r)
    {
        int mid=(l+r)/2;
        if(check(mid))
            r=mid;
        else
            l=mid+1;
    }
    if(l==1e7)
        printf("-1\n");
    else
        printf("%lld\n",l);
    return ;
}
signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
       solve();
    return 0;
}

1003.Cyber Language(签到)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =5e5+10,mod=998244353;
void solve()
{
    string s;
    getline(cin,s);
    cout<<(char)(s[0]-32);
    for(int i=0;i<s.size();i++)
    {
        if(s[i]==' ')
            cout<<(char)(s[i+1]-32);
    }
    cout<<endl;
    return ; 
}
signed main()
{
    int t;
    cin>>t;
    getchar();
    while(t--)
       solve();
    return 0;
}

1009.Package Delivery

题意:n个包裹,每个包裹有一个可以存放在快递站的时间区间[l,r].小Q每次可以拿k个物品,并且一天可以去多次快递站,问最少几次拿完包裹.

思路:对于这些包裹,我们如果在它们到期的时候去拿,可以保证攒下来一大堆包裹,就可以达到尽量少去的效果.只需要将包裹按照l排序,对于第一个遇到的r将所有和这个区间有交集的区间存入以r从小到大排序的优先队列中去.每次拿k个或者直接拿完,判断哪种更优.

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =1e5+10,mod=998244353;
struct node
{
    int l,r;
}edge[N];
bool cmp(node a,node b)
{
    if(a.r!=b.r)
        return a.l<b.l;
    else
        return a.r<b.r;
}
void solve()
{
    int n,k,ans=0,i,cnt=0;
    priority_queue<int,vector<int>,greater<int>>qu;
    scanf("%lld%lld",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&edge[i].l,&edge[i].r);
    sort(edge+1,edge+1+n,cmp);
    for(i=1;i<=n;i++) 
    {
        int l=edge[i].l;
        while(qu.size()&&l>qu.top())
        {
            ans++;
            cnt=max(0ll,cnt-k);
            while(qu.size()!=cnt)
                qu.pop();
        }
        cnt++;
        qu.push(edge[i].r);
    }
    ans+=ceil(cnt*1.0/k);
    printf("%lld\n",ans);
    return ;
}
signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
       solve();
    return 0;
}

1011.Taxi

题意:给你n个点,每个点有坐标xi,yi和一个权值wi.有q次询问,每次包含有小Q的位置x,y.小Q有一张vip卡,要坐车去第i个点的话,每次可以减免min(|x-xi|+|y-yi| , w).问当前情况小Q最多减免多少钱.

思路:先不考虑wi的话,每次的最大车费就是距离小Q最远的点,我们可以对式子变形一下:

只需要对上述四个值维护最大值,并且按照式子计算即可o1的时间内求出距离小Q最长的距离.

那么包含w之后,就要看w对这个长度有什么影响了.我们可以把w按照从小到大的顺序排序,在去维护一个后缀的上图中的四个最大值.已知w此时就是升序排列,而后缀的四种情况是非递增的情况.这个时候就考虑进行二分,我们对于每次的mid进行check,当当前情况下的w[mid]<=最长路(用那四个变量维护的).此时我们考虑往右转移因为此时结果不受最长路的长度影响,只受w影响,而w又是升序排列,我们就往后考虑.而w[mid]>最长路时,此时就要结果只受最长路长度的影响,那么最长路长度又是按照风递增的情况排列,直接往左找即可.

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N =1e5+10,inf=-1e18;
struct node
{   
    int x,y,w;
    bool operator<(const node &A)const{
        return w<A.w;
    }
}edge[N];
int res,a[N],b[N],c[N],d[N];
bool check(int x,int y,int mid)
{
    int ans=inf,w=edge[mid].w;
    ans=max(ans,x+y+a[mid]);
    ans=max(ans,-x+y+b[mid]);
    ans=max(ans,x-y+c[mid]);
    ans=max(ans,-x-y+d[mid]);
//ans就是利用维护的四个值求最长路的值
    res=max(res,min(w,ans));
    return w<=ans;
}
void solve()
{
    int n,q;
    scanf("%lld%lld",&n,&q);
    for(int i=0;i<n;i++)
        scanf("%lld%lld%lld",&edge[i].x,&edge[i].y,&edge[i].w);
    sort(edge,edge+n);
    a[n]=b[n]=c[n]=d[n]=inf;
    for(int i=n-1;i>=0;i--)
    {
        int x=edge[i].x,y=edge[i].y,w=edge[i].w;
        a[i]=max(a[i+1],-x-y);
        b[i]=max(b[i+1],x-y);
        c[i]=max(c[i+1],-x+y);
        d[i]=max(d[i+1],x+y);
    }
    while(q--)
    {
        int x,y;
        res=inf;
        scanf("%lld%lld",&x,&y);
        int l=0,r=n-1;
        while(l<r)
        {
            int mid=(l+r+1)/2;
            if(check(x,y,mid))
                l=mid;
            else
                r=mid-1;
            check(x,y,l);
        }
        printf("%lld\n",res);
    }
    return ;    
}
signed main()
{
    int t;
    scanf("%lld",&t);
    while(t--)
       solve();
    return 0;
}

1012.Two Permutations

题意:有两个全排列a,b,还有一个空的容器,我们每次可以把两个全排列的首位放到容器尾部,问最后容器里的排列有多少种情况和题目所给的s数组相等.

思路:我们定义x,y为a的第x位要和s匹配,b的第y位和s进行匹配,b[y]表示,f[i][0/1]表示长度为i时以a结束还是0结束.我们采取记忆化搜索的做法,当a[x]==s[x+y-1]&&x<=n我们直接向着x+1,y,0的状态转移的,b[y]==s[x+y-1]&&y<=n我们直接向着x,y+1,0的状态转移.本来状态应该是f[N][N][0/1]的,但是会超限.我们就压缩成f[N*2][0/1]的话,会发现有边界条件之后,可以把状态压缩为字符串的长度,不会对结果有影响.

f[x+y-1][ty]=f[1+m-1][ty]+f[2+(m-1)-1][ty]+...+f[m+1-1][ty]
(m==x+y-1)

因为如果x到达了边界n就不会往下搜,y同理,所以f[i][0/1]中不合法的情况(两者没有同时到达n)是不会转移到最后的结果:f[2*n][0]+f[2*n][1]的.所以这样压缩状态是正确的

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> pii;
const int N =3e5+10,mod=998244353;
int n,a[N],b[N],s[2*N];
int f[N*2][2]; 
int dp(int x,int y,int ty)
{
    if(x>n&&y>n)
        return 1;
    if(f[x+y-1][ty]!=-1)
        return f[x+y-1][ty];
    int ans=0;
    if(a[x]==s[x+y-1]&&x<=n)
    {
        ans+=dp(x+1,y,0);
        ans%=mod;
    }
    if(b[y]==s[x+y-1]&&y<=n)
    {
        ans+=dp(x,y+1,1);
        ans%=mod;
    }
    return f[x+y-1][ty]=ans%mod;
}
void solve()
{
    memset(f,-1,sizeof f);
    cin>>n;
    f[1][1]=0;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    for(int i=1;i<=n;i++)
        cin>>b[i];
    for(int i=1;i<=2*n;i++)
        cin>>s[i];
    if(a[1]==s[1])
    {
        f[1][1]+=dp(2,1,0);
    }
    if(a[1]==s[2]);
    {
        f[1][1]+=dp(1,2,1);
    }
    cout<<f[1][1]%mod<<endl;
    return ;
}
signed main()
{
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(0);
    int t;
    cin>>t;
    while(t--)
       solve();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值