文章目录
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 n≤18,所以可以状压,共有 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(∣x−xi∣+∣y−yi∣, 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(∣x−xi∣+∣y−yi∣, wi)(1≤i≤n)
m a x { ∣ x − x i ∣ + ∣ y − y i ∣ } max\{|x−x_i|+|y−y_i|\} max{∣x−xi∣+∣y−yi∣}
= 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(x−xi,−x+xi)+max(y−yi,−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{x−xi+y−yi, −x+xi+y−yi, x−xi−y+yi, −x+xi−y+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), (x−y)−(xi−yi), (−x+y)+(xi−yi), (−x−y)+(xi+yi)}
将所有城镇按照 w w w 从小到大排序,并记录排序后每个后缀的 x i + y i 、 x i − y i x_i + y_i、x_i − y_i xi+yi、xi−yi 的最大值和最小值,用于 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 k…n 个城镇的距离最大值 d d d ,有两种情况:
• w k < d w_k < d wk<d,那么第 k … n k\dots n k…n 个城镇对答案的贡献至少为 w k w_k wk。用 w k w_k wk 更新答案后,由于第 1 … k 1\dots k 1…k 个 城镇的 w w w 值均不超过 w k w_k wk,因此它们不可能接着更新答案,考虑范围缩小至 [ k + 1 , n ] [k + 1, n] [k+1,n]。
• w k ≥ d w_k ≥ d wk≥d,那么第 k … n k\dots n k…n 个城镇对答案的贡献为 d d d 。用 d d d 更新答案后,考虑范围缩小至 [ 1 , k − 1 ] [1, k − 1] [1,k−1]。
代码
#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(1≤T≤300), 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(1≤n≤300000), 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(1≤Pi≤n).
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(1≤Qi≤n).
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(1≤Si≤n).
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)
(P或Q),将其最左端元素添加到R的最右端,之后你可以得到一个长度为
2
n
2n
2n的序列R。
再给定一个长度为
2
n
2n
2n的序列
S
S
S,输出使得
R
=
S
R = S
R=S的方案数。答案对
998244353
998244353
998244353 取模。
难点:
- 数据范围比较大,时间复杂度要控制在 O ( n ) O(n) O(n)
- 状态转移比较具有跳跃性
解题思路
定义 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 1,2 ,由于 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 1,3,2 ,不符合 q q q 数组的顺序, 2.匹配 s s s 中的第 3 , 5 3, 5 3,5 个数,这时剩下的数的顺序是 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 1−N , 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 ;
}
}