2022杭电多校联赛第三场 题解

比赛传送门
作者: fn


签到题

1003题 Cyber Language / 网络语言

题目大意
找到每个字符串的第一个字母,变成大写字母并输出。

考察内容
签到

分析
按题意模拟即可。

#include<bits/stdc++.h>
#include<string>
using namespace std;
string s;
int t;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
int main(){
	js;    
    cin>>t;
	getline(cin,s);
    while(t--){
		getline(cin,s);
		bool flag = false;
		for(int i = 0;i < s.length();i++){
			if(i==0) cout<<(char)(s[i]-32); // 转大写 
			else{
				if(flag){
					cout<<(char)(s[i]-32); // 转大写 
					flag = false;
				}
				else if(s[i]==' '){
				 	flag = true;	
				}	
			}
		}
		cout<<endl;
	}
}

基本题

1009题 Package Delivery / 取快递

题目大意
给定 n n n 个区间,每次操作选择一个点,把最多 k k k 个经过该点区间消除。
求最少操作次数。

考察内容
排序,贪心,复杂度优化

分析
先按照区间右端点排序,枚举右端点即可。
m i n l minl minl 数组预处理后缀的最小左端点,优化一下就能通过。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;

ll n,k;
struct node{
	ll l,r;
	int id;
}a[N];

bool cmp(node x,node y){
	if(x.r!=y.r)return x.r<y.r;
	if(x.l!=y.l)return x.l<y.l;
	return x.id<y.id;
}

bool vis[N];
ll minl[N]; 

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n>>k;
		for(int i=1;i<=n;i++){
			cin>>a[i].l>>a[i].r;
			a[i].id=i;
		}
		sort(a+1,a+n+1,cmp); // 按照右端点升序排序
		
		minl[n]=a[n].l; // 记录左端点最小值 
		for(int i=n-1;i>=1;i--){
			minl[i]=min(minl[i+1],a[i].l);
		}
		
		memset(vis,0,sizeof(vis[0])*(n+1));
		
		ll ans=0;
		
		if(k==1){ // 如果一次只能拿一件 
			cout<<n<<endl; // 答案就是n次 
			continue;
		} 
		
		// k>=2时 
		// O(n^2)暴力+贪心做法 
		for(int i=1;i<=n;i++){ // 枚举右端点 
			if(vis[i])continue;
			
			ll p=a[i].r; // 在右端点时才去拿快递(ddl战士) 
			ans++; // 加一次 
			ll cnt=1; // 目前拿了1件 
			vis[i]=1; // 用上 
			
			for(int j=i+1;j<=n;j++){ // 往后找可以顺带带走的区间 
				if(cnt>=k)break; // 拿不下了,break 
				if(vis[j])continue; 
				
				if(minl[j]>p){
					break; // 后面都找不到minl更小的了,提前截止 
				}
				
				if(a[j].l<=p){ // a[j]左端点<=拿快递时间,顺带带走
					cnt++; // 多拿1件 
					vis[j]=1; 
				}
			}
			
			int h=2000;
			if(i%h==0){ // 每h个i重新建立minl数组 
				minl[n+1]=1e18; 
				
				// 记录左端点最小值 
				for(int i=n;i>=1;i--){
					if(vis[i])minl[i]=minl[i+1];
					else minl[i]=min(minl[i+1],a[i].l);
				}
			} 
		} 
		
		cout<<ans<<endl;
	}
	return 0;
}
/*
输入: 
1
4 2

1 7
2 6
3 5
7 8

输出:
2
// 5,7两天 

*/ 

进阶题

1012题 Two Permutations / 两个排列

题目大意
给定两个排列队列 P , Q P, Q P,Q ,求出队后形成序列 S S S 的方案数量。

考察内容
动态规划,复杂度优化,字符串哈希

分析
方法一:
二维dp, f [ i ] [ j ] f[i][j] f[i][j] 的状态表示是 S S S 的前 i i i 个用了 P P P 的前 j j j 个。
把第一位滚动掉,并用unordered_map存状态。
时间复杂度 O ( n ) O(n) O(n)

方法一代码:

#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define ll long long
#define N 600005
#define mod 998244353
using namespace std;
template <typename T>
void inline read(T& x) {
    int f = 1;
    x = 0;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            f = -1;
        s = getchar();
    }
    while (s <= '9' && s >= '0')
        x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

int t, n, a[N], b[N], c[N], p[N], q[N];
void inline add(int& x, int y) { // 加法取模 
    x += y;
    if (x >= mod)
        x -= mod;
}

void solve() {
    unordered_map<ll, int> mp[2];
    
    // 读入数据 
    read(n);
    for (int i = 1; i <= n; i++)
        read(a[i]), p[a[i]] = i;
    for (int i = 1; i <= n; i++)
        read(b[i]), q[b[i]] = i;
    for (int i = 1; i <= (n << 1); i++)
        read(c[i]);
    
    mp[0][0] = 1; 
    for (int i = 1; i <= (n << 1); i++){ // 枚举s的每一位 
        mp[i & 1].clear();
        int j = p[c[i]];
        int k = q[c[i]];
        if (j > 0 && j <= min(i, n))
            add(mp[i & 1][j], mp[i - 1 & 1][j - 1]);

        if (i >= k)
            add(mp[i & 1][i - k], mp[i - 1 & 1][i - k]);
    }
    printf("%d\n", mp[0][n]);
}

int main() { // dp
    read(t);
    while (t) {
        t--;
        solve();
    }
    return 0;
}

方法二(更快):

dp+字符串哈希。

在这里插入图片描述

字符串哈希传送门

方法二代码:

#include<cstdio>
typedef unsigned long long ull;
const int N=300005,P=998244353;
const int 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){ // 判断序列b,c中的两段子串是否相等 
  	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(){ // dp+字符串哈希 
	// 预处理常数S的幂次,存入p数组 
  	for(p[0]=i=1;i<N*2;i++)p[i]=p[i-1]*S;
  	
  	scanf("%d",&Case);
  	while(Case--){
  		// 输入数据 
  	  	scanf("%d",&n);
  	  	
  	  	// 初始化pc 
		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; // 数字x第1次出现 
			else pc[x][1]=i; // 数字x第2次出现 
  	  	}
  	  	
  	  	// 特判序列 S 中每个数字出现次数不都为 2 的情况
  	  	for(i=1;i<=n;i++)if(!pc[i][0]||!pc[i][1])break;
  	  	if(i<=n){
  	  	  	puts("0"); // 此时答案为 0
  	  	  	continue;
  	  	}
  	  	
  	  	// 初始化f 
  	  	for(i=1;i<=n;i++)for(j=0;j<2;j++)f[i][j]=0;
  	  	
  	  	// dp转移,时间复杂度 O(n)
  	  	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++){ // 枚举 P 的前 i 项匹配上了 S 
			for(j=0;j<2;j++){ // Pi 匹配 S 中数字 Pi 第 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); // 输出 
  	}
}

1002题 Boss Rush / 打Boss

题目大意

boss有 H H H 单位的生命值。
你会使用 n n n 个技能,每个技能最多使用1次。每个技能释放消耗的时间为 t i t_i ti ,造成的伤害是一个序列。

游戏从第0帧开始,求打败Boss的所需最少时间,如果无法打败Boss输出 “-1” 。

考察内容
动态规划,二分答案

分析
二分答案,把问题转化为判断 T T T 帧内能否打败 Boss,即求出 T T T 帧内能打出的最高伤害,判断是否大于等于 H H H
在这里插入图片描述

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值