2022暑期杭电第三场

本文概述了四道编程题目的关键点:1002题BossRush中利用二分查找策略确定最短击杀时间,1009题PackageDelivery通过优化取快递次数实现最小操作次数,1011题Taxi借助哈希解决距离计算问题,以及1012题TwoPermutations通过dp解决字符串合并问题。每个问题都展示了不同的算法技巧和数据结构应用。
摘要由CSDN通过智能技术生成

1002题

Boss Rush

题目链接

题意

T T T ( ≤ 100 \leq100 100)组样例,每组样例先给出技能数量 n n n ( ≤ 18 \leq18 18)和 B o s s Boss Boss 血量 H H H ( ≤ 1 0 18 \leq10^{18} 1018),接下来 2 2 2 × \times × n n n 行,每个技能的第一行是技能使用时间 t [ i ] t[i] t[i]( ≤ 1 0 5 \leq10^5 105)和技能持续时间 l e n [ i ] len[i] len[i]( ≤ 1 0 5 \leq10^5 105),接下来第二行 l e n [ i ] len[i] len[i] 个数 d [ i ] [ j ] d[i][j] d[i][j] ( ≤ 1 0 9 \leq10^9 109 )表示技能持续的每一秒内对 B o s s Boss Boss 造成的伤害。技能使用期间( t [ i ] t[i] t[i])无法使用其他技能,问杀死 B o s s Boss Boss 的最短时间,无法杀死输出 − 1 -1 1.

分析

询问杀死 B o s s Boss Boss 的最短时间,可以转化为在 t t t 时间内能否杀死 B o s s Boss Boss ,就是求 t t t 时间内能打出的最高伤害是否大于 H H H ,因为伤害是单调递增的,所以可以使用二分求解。

由于 n ≤ 18 n\leq18 n18,所以可以状压,共有 1 < < 18 1<<18 1<<18 个状态表示已使用的技能。 f [ p ] f[p] f[p] 表示状态 p p p 时在时间t内造成的最大伤害,枚举状态 p p p ,状态 p p p 时枚举还没有使用的技能,如果使用技能后时间未超过 t t t ,那么就将伤害计入 f [ p ∣ ( 1 < < i ) ] f[p|(1<<i)] f[p(1<<i)] ,否则,只计算其在 t t t 时间前的伤害。其中到达状态 p p p 时,如果当前伤害值大于 H H H,直接返回 t r u e true true,如果一直更新结束也没有状态的伤害值大于 H H H ,返回 f a l s e false false

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;

const int N=18+1;
const int L=1e5+1;

ll T,n,H;
ll t[N],len[N],d[N][L],tim[1<<N],f[1<<N];

bool ask(ll t)
{
    f[0]=0;
    for(register int i=1;i<(1<<n);i++) f[i]=-1;
    for(register int p=0;p<(1<<n);p++)
    {
        if(f[p]>=H) return 1;
        else if(f[p]==-1) continue;
        else if(tim[p]>t) continue;
        else
            for(register int i=0;i<n;i++)
            {
                if((p>>i)&1) continue;
                if(tim[p]+len[i]-1<=t) f[p|(1<<i)]=max(f[p|(1<<i)],f[p]+d[i][len[i]]);
                else f[p|(1<<i)]=max(f[p|(1<<i)],f[p]+d[i][t-tim[p]+1]);
            }
    }
    return 0;
}

int main()
{
    scanf("%lld",&T);
    while(T--)
    {
        scanf("%lld%lld",&n,&H);
        for(register int i=0;i<n;i++)
        {
            scanf("%lld%lld",&t[i],&len[i]);
            for(register int j=1;j<=len[i];j++)
            {
                scanf("%lld",&d[i][j]);
                d[i][j]+=d[i][j-1];
            }
        }
        //状态p所需的时间tim
        memset(tim,0,sizeof(tim));
        for(register int p=1;p<(1<<n);p++)
            for(register int j=0;j<=n-1;j++)
                if((p>>j)&1) tim[p]+=t[j];
        //二分答案
        ll l=0,r=1e18,m;
        while(l<r)
        {
            m=(l+r)/2;
            if(ask(m)) r=m;
            else l=m+1;
        }
        if(ask(l)) printf("%lld\n",l);
        else printf("-1\n");
                    
    }   
    return 0;
}

1009

Package Delivery

题目链接

题意

n n n 个快递,每个快递有送达时间和退还时间。每次可以选择一天拿快递,一次拿 K K K 个快递,一天可以拿无数次,问如何在最少的次数下取到所有快递。

思路

对于快递 a i a_i ai ,在第 r i r_i ri 天取的时候积累的快递一定是最多的。此外为了使次数最少,每次拿的快递要尽可能的多。因此,我们需要遍历 r i r_i ri 并计算当天的快递数量是否是 k k k 的倍数,如果是则没有浪费次数,如果不是则需要取出下一批将要退回的快递凑成 k k k 的倍数。

代码

#include <iostream>
#include <climits>
#include <string.h>
#include <string>
#include <stdio.h>
#include <queue>
#include <vector>
#include <map>
#include <set>
#include <list>
#include <stack>
#include <algorithm>
#include <math.h>
#include <cctype>
#include <stdlib.h>
#include <unordered_map>
#define INTINF 0x3f3f3f3f
#define LLINF  0x3f3f3f3f3f3f3f3f
#define N 2000010
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;

int t;
int n,k;
vector<PII>a;
vector<int>v;
int main()
{
    cin>>t;
    while(t--)
    {
        a.clear();
        v.clear();
        priority_queue<int,vector<int>,greater<int>>q;//小顶堆维护累计的快递
        cin>>n>>k;
        for(int i=0;i<n;i++)
        {
            int l,r;
            cin>>l>>r;
            a.push_back({l,r});
            v.push_back(r);
        }
        sort(a.begin(),a.end());//按first排序
        sort(v.begin(),v.end());

        int now=0;
        int ans=0;
        for(int i=0;i<n;i++)
        {
            int cnt=0;
            while(a[now].first<=v[i]&&now<n)
            {
                q.push(a[now++].second);
            }
            while(q.size()&&q.top()==v[i])
            {
                cnt++;
                q.pop();
            }
            ans+=cnt/k;
            if(cnt%k==0) continue;
            int x=k-cnt%k;
            while(q.size()&&x)
            {
                x--;
                q.pop();
            }
            ans++;
        }
        cout<<ans<<endl;
    }

    system("pause");
    return 0;
}

1011题

Taxi

题目链接

题意:

提供一些坐标点 ( x i , y i ) (x_i,y_i) (xi,yi) 及他们的权值 w i w_i wi,多次询问,求给定 ( x , y ) (x,y) (x,y) 与其它点的 m i n ( ∣ x − x i ∣ + ∣ y − y i ∣ ,   w i ) min(|x−x_i|+|y−y_i|,\ w_i) min(xxi+yyi, wi) 最大值。

思路:

如果没有 w w w 的限制,那么是经典问题。根据 ∣ x ∣ = m a x ( x ,   − x ) |x| = max(x,\ −x) x=max(x, x) ,有
m i n ( ∣ x − x i ∣ + ∣ y − y i ∣ ,   w i ) ( 1 ≤ i ≤ n ) min(|x−x_i|+|y−y_i|,\ w_i) (1≤i≤n) min(xxi+yyi, wi)(1in)

m a x { ∣ x − x i ∣ + ∣ y − y i ∣ } max\{|x−x_i|+|y−y_i|\} max{xxi+yyi}

= m a x { m a x ( x − x i , − x + x i ) + m a x ( y − y i , − y + y i ) } =max\{max(x−x_i,−x+x_i)+max(y−y_i,−y+y_i)\} =max{max(xxi,x+xi)+max(yyi,y+yi)}

= m a x { x − x i + y − y i ,   − x + x i + y − y i ,   x − x i − y + y i ,   − x + x i − y + y i } =max\{x−x_i+y−y_i,\ −x+x_i+y−y_i,\ x−x_i−y+y_i,\ −x+x_i−y+y_i\} =max{xxi+yyi, x+xi+yyi, xxiy+yi, x+xiy+yi}

= m a x { ( x + y ) − ( x i + y i ) ,   ( x − y ) − ( x i − y i ) ,   ( − x + y ) + ( x i − y i ) ,   ( − x − y ) + ( x i + y i ) } =max\{(x+y)-(x_i+y_i),\ (x−y)-(x_i-y_i),\ (−x+y)+(x_i-y_i),\ (−x−y)+(x_i+y_i)\} =max{(x+y)(xi+yi), (xy)(xiyi), (x+y)+(xiyi), (xy)+(xi+yi)}

将所有城镇按照 w w w 从小到大排序,并记录排序后每个后缀的 x i + y i 、 x i − y i x_i + y_i、x_i − y_i xi+yixiyi 的最大值和最小值,用于 O ( 1 ) O(1) O(1) 求给定点 ( x ,   y ) (x,\ y) (x, y) 到该后缀中所有点 的距离最大值。

选取按 w w w 排序后的第 k 个城镇, O ( 1 ) O(1) O(1) 求出给定点 ( x ,   y ) (x,\ y) (x, y) 到第 k … n k\dots n kn 个城镇的距离最大值 d d d ,有两种情况:

w k < d w_k < d wk<d,那么第 k … n k\dots n kn 个城镇对答案的贡献至少为 w k w_k wk。用 w k w_k wk 更新答案后,由于第 1 … k 1\dots k 1k 个 城镇的 w w w 值均不超过 w k w_k wk,因此它们不可能接着更新答案,考虑范围缩小至 [ k + 1 , n ] [k + 1, n] [k+1,n]

w k ≥ d w_k ≥ d wkd,那么第 k … n k\dots n kn 个城镇对答案的贡献为 d d d 。用 d d d 更新答案后,考虑范围缩小至 [ 1 , k − 1 ] [1, k − 1] [1,k1]

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1e5+10;

int t,n,m,qx,qy,ans;
int a[N],b[N],c[N],d[N];

struct Node{
	int x,y,w;
}p[N];

bool cmp(Node a,Node b){
	return a.w<b.w;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].w);
		sort(p+1,p+n+1,cmp);
		a[n] = c[n] = p[n].x+p[n].y;
		b[n] = d[n] = p[n].x-p[n].y;
		for(int i=n-1;i>=1;i--){
			a[i]=max(a[i+1],p[i].x+p[i].y);
			b[i]=max(b[i+1],p[i].x-p[i].y);
			c[i]=min(c[i+1],p[i].x+p[i].y);
			d[i]=min(d[i+1],p[i].x-p[i].y);
		}
		while(m--){
			scanf("%d%d",&qx,&qy);
			ans = 0;
			int l = 0,r = n;
			while(l<r){
				int mid = l+r+1>>1;
				int md = qx+qy-c[mid];
				md = max(md,qx-qy-d[mid]);
				md = max(md,-qx+qy+b[mid]);
				md = max(md,-qx-qy+a[mid]);
				if(md >= p[mid].w){
					ans = max(ans,p[mid].w);
					l = mid;
				}else{
					ans = max(ans,md);
					r = mid - 1;
				}
			}
			printf("%d\n",ans);
		}
	}
}

1012题

Two Permutations

题目链接

题目描述

There are two permutations P 1 , P 2 , … , P n , Q 1 , Q 2 , … , Q n P_1,P_2,…,P_n, Q_1,Q_2,…,Q_n P1,P2,,Pn,Q1,Q2,,Qn and a a a sequence R R R. Initially, R R R is empty. While at least one of P P P and Q Q Q is non-empty, you need to choose a non-empty array ( P P P or Q Q Q), pop its leftmost element, and attach it to the right end of R R R. Finally, you will get a a a sequence R R R of length 2 n 2n 2n.

You will be given a sequence S S S of length 2 n 2n 2n, please count the number of possible ways to merge P P P and Q Q Q into R R R such that R = S R=S R=S. Two ways are considered different if and only if you choose the element from different arrays in a step.

输入

The first line contains a a a single integer T ( 1 ≤ T ≤ 300 ) T (1≤T≤300) T(1T300), the number of test cases. For each test case:

The first line contains a a a single integer n ( 1 ≤ n ≤ 300000 ) n (1≤n≤300000) n(1n300000), denoting the length of each permutation.

The second line contains n n n distinct integers P 1 , P 2 , … , P n ( 1 ≤ P i ≤ n ) P_1,P_2,…,P_n (1≤P_i≤n) P1,P2,,Pn(1Pin).

The third line contains n n n distinct integers Q 1 , Q 2 , … , Q n ( 1 ≤ Q i ≤ n ) Q_1,Q_2,…,Q_n (1≤Q_i≤n) Q1,Q2,,Qn(1Qin).

The fourth line contains 2 n 2n 2n integers S 1 , S 2 , … , S 2 n ( 1 ≤ S i ≤ n ) S_1,S_2,…,S_{2n} (1≤S_i≤n) S1,S2,,S2n(1Sin).

It is guaranteed that the sum of all n n n is at most 2000000 2000000 2000000.

输出

For each test case, output a single line containing an integer, denoting the number of possible ways. Note that the answer may be extremely large, so please print it modulo 998244353 998244353 998244353 instead.

样例输入

2

3

1 2 3 

1 2 3 

1 2 1 3 2 3

2 

1 2 

1 2 

1 2 2 1

样例输出

2 

0

题解:

涉及算法: d p dp dp , 字符串哈希,

题目大意:

给定两个排列 P 1 , P 2 , … … P n P_1,P_2,……P_n P1,P2,……Pn, Q 1 , Q 2 , … … Q n Q_1,Q_2,……Q_n Q1,Q2,……Qn和一个空序列R,保证 P P P, Q Q Q 至少有一个非空。你需要选择一个非空排列 ( P 或 Q ) (P或Q) (PQ),将其最左端元素添加到R的最右端,之后你可以得到一个长度为 2 n 2n 2n的序列R。
再给定一个长度为 2 n 2n 2n的序列 S S S,输出使得 R = S R = S R=S的方案数。答案对 998244353 998244353 998244353 取模。

难点:

  1. 数据范围比较大,时间复杂度要控制在 O ( n ) O(n) O(n)
  2. 状态转移比较具有跳跃性

解题思路

定义 d p dp dp 用的数组 d p [ i ] [ j ] dp[i] [j] dp[i][j] , 表示p的前i项匹配上了 s s s ,且 p [ i ] p[i] p[i] 匹配 s s s 中数字 p [ i ] p[i] p[i] j j j 次出现的位置时,有多少种合法方案 ;

举例解释dp含义:

​例如第一个样例 d p [ 3 ] [ 1 ] dp[3] [1] dp[3][1] 表示 p数组的前三项都匹配上了 s s s , 且最后这个 3 3 3 匹配上 s s s 中第二次出现的 s s s(也就是最后一个数),对于 p p p 的前面两个数 1 , 2 1 ,2 12 ,由于 s s s 在最后一个 3 3 3 前面有两个 1 1 1 和两个 2 2 2 ,这时就可能有多种匹配方案,比如 1 1 1 匹配 s s s 中的第 1 , 2 1,2 1,2 个数,

这时剩下的数的顺序是 1 , 3 , 2 1 ,3 ,2 132 ,不符合 q q q 数组的顺序, 2.匹配 s s s 中的第 3 , 5 3, 5 35 个数,这时剩下的数的顺序是 1 , 2 , 3 1,2,3 1,2,3 ,符合 q q q 数组的顺序,所以可以,因此 d p [ 3 ] [ 1 ] dp[3] [1] dp[3][1] 的值就是 1 1 1

i i i 的范围是 1 − N 1-N 1N j j j 0 0 0 或者 2 2 2 所以 复杂度是 O ( n ) O(n) O(n) 级别的 ;

状态转移:枚举 p [ i + 1 ] p[i+1] p[i+1] 匹配哪个位置,判断 p [ i ] p[i] p[i] 匹配的位置与 p [ i + 1 ] p[i+1] p[i+1] 匹配的位置中间的那段连续子序列是否完全匹配 q q q 中对应的子串(这里用字符串哈希判断,预处理之后只需要 O ( 1 ) O(1) O(1) 的时间)如果完全匹配,则转移成功

代码

#include <bits/stdc++.h>
using namespace std ;
// 字符串哈希中要预处理S进制,可能会爆int
typedef long long LL ; 
const int S = 233 , N = 300005 , mod = 998244353 ; 
int n ; 
int a[N] , b[N] , c[2*N] , dp[N][2], pc[N][2] ; 
LL  hb[N] , hc[2*N]  ,p[N *2 ];

// 返回数组中一段的哈希值
LL ask( LL *f ,int l ,int r )
{
    return f[r] - f[l-1] * p[r - l + 1 ] ; 
}
//判断两个数组中的两部分是否相等
bool check(int bl , int br , int cl , int cr)
{
	// 如果区间长度为0,说明p[i+1]和p[i]是连续的,不用判断,直接返回true
    if(bl > br) return 1 ; 
    if(bl < 1 || br > n || cl <1 || cr > n+n) return 0 ;
    //判断是否相同
    return ask(hb , bl ,br ) == ask(hc , cl , cr ) ;
}
int main()
{
	// 预处理S进制的i次方
    p[0] = 1 ; for(int i = 1 ;i <= 2 * N ; i++) p[i] = p[i-1] * S ; 
    int T ; 
    scanf("%d", &T) ; 
    while(T--)
    {
        memset(pc , 0 , sizeof pc) ; 
        memset(dp , 0 , sizeof dp) ;
        memset(hb ,0 , sizeof hb) ;
        memset(hc , 0 , sizeof hc) ; 
        cin >> n ; 
        for(int i  = 1 ; i<= n ;i++) scanf("%d" , &a[i]) ; 
        for(int i =  1 ; i<= n; i++) scanf("%d" , &b[i]) , hb[i] = hb[i-1] * S + b[i] ; 
		// 处理b和c数组的哈希值
        for(int i = 1 ; i <= 2 * n ; i ++) scanf("%d", &c[i]) , hc[i] = hc[i-1] *S + c[i] ;
        
		//由于要用到每个数字在s中出现的位置,所以这里用pc数组记录一下
        for(int i = 1; i<= 2 *n ; i++)
        {
            int x = c[i] ; 
            if(pc[x][0] == 0) pc[x][0] = i ; 
            else pc[x][1] = i ; 
        }
        //特判s中的1-n中的每个数出现次数不都为2的情况,这种情况方案数必定为0
        int flag = 0 ; 
        for(int i =1 ; i<= n ;i++)
        {
            if(!pc[i][0] || !pc[i][1] )
            {
                flag = 1 ; 
                break ; 
            }
        }
        if(flag) 
        {
            cout << 0 << endl ; 
            continue ; 
        }
        //初始化匹配上一个数的情况
        for(int j = 0 ; j<2 ; j++)
        {
            int x = pc[a[1]][j] ; 
           	//第一个数就匹配到了第x位,所以前x-1个数由q数组匹配,判断q和s的这一段是否相同,相同则赋值为1
            if(check(1 , x-1 , 1 , x-1 )) dp[1][j] = 1 ; 
        }
        
        for(int i = 1; i < n ; i++)
        for(int j = 0 ; j <2 ; j++)
        if(dp[i][j])
        {
        	//a[i]在s中出现第j+1次对应的下标
            int u = pc[a[i]][j] ; 
            for(int k = 0 ; k < 2 ; k++)
            {
            	//a[i]在s中出现第k+1次对应的下标
                int x = pc[a[i+1]][k] ; 
                if(x <= u) continue ; 
                //u+1 到 x-1 是s中要判断的区间,q中要判断的区间长度和s相同,位置是s判断的区间向左移动i个单位
                if(check(u+1-i,x-1-i, u+1 , x-1)) dp[i+1][k] = (dp[i+1][k]+dp[i][j])%mod ; 
            }
        }
        int ans=0 ; 
        //这里不能直接加上f[n][1]和f[n][0] ,因为a[n]对应的s中的位置不一定是最后一位,要判断pc[a[n]][j]后面一段是否和q中的对应段相同
        for(int i = 0 ;i <2 ; i++)
        {
            if(dp[n][i] == 0 ) continue ; 
            int u = pc[a[n]][i] ; 
            if(check( u + 1 - n , n  , u + 1 , n + n )) ans = (ans + dp[n][i])%mod ; 
        }
        cout <<ans << endl ;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值