UPC训练赛十八:H-High Load Database

题目描述
Henry profiles a high load database migration script. The script is the list of n transactions. The i-th transaction consists of ai queries. Henry wants to split the script to the minimum possible number of batches, where each batch contains either one transaction or a sequence of consecutive transactions, and the total number of queries in each batch does not exceed t.
Unfortunately, Henry does not know the exact value of t for the production database, so he is going to estimate the minimum number of batches for q possible values of t: t1, t2, … , tq. Help Henry to calculate the number of transactions for each of them.
输入
The first line contains a single integer n — the number of transactions in the migration script (1 ≤ n ≤ 200 000).
The second line consists of n integers a1, a2,… , an — the number of queries in each transaction (1 ≤ ai; ∑ai ≤ 106).
The third line contains an integer q — the number of queries (1 ≤ q ≤ 100 000).
The fourth line contains q integers t1, t2,… , tq (1 ≤ ti ≤∑ai).
输出
Output q lines. The i-th line should contain the minimum possible number of batches, having at most ti queries each. If it is not possible to split the script into the batches for some ti, output “Impossible”
instead.
Remember that you may not rearrange transactions, only group consecutive transactions in a batch.
样例输入 Copy
6
4 2 3 1 3 4
8
10 2 5 4 6 7 8 8
样例输出 Copy
2
Impossible
4
5
4
3
3
3


题目大意:给出一个长度为 n 的数列,再给出 m 次询问,每次询问给出一个阈值 x ,问最少将数列分割成多少段,可以使得每一段的总和都不超过 x,无解的话输出 Impossible
n到2e5,m到1e5,数列总和不超过1e6

方法一:
考虑优化,因为数列中的每个元素都是正整数,所以维护的前缀和一定是递增的,所以对于每段区间可以进行二分去划分,比如初始时未经过划分的总区间为 [ 1 , n ] ,设阈值为 x ,我们可以二分出一个最大的 pos,使得 sum[ pos ] - sum[ 0 ] <= x,这样可以直接将区间 [ 1 , pos ] 划分为第一段,然后再去给区间 [ pos + 1 , n ] 进行划分,这样做的时间复杂度最坏还是会退化为 O( n ) 的。
光光这样也过不了,一开始我们队就卡在这边。还有要用数组记忆化一下。
这卡时间就离谱。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=1e6+100;
int n,m,mmax,sum[N],ans[N];
int solve(int x)
{
    
    
	if(ans[x])	return ans[x];
	int last=1;
	ans[x]=0;
	while(last<=n){
    
    
		ans[x]++;
		int l=last,r=n,ans=-1;
		while(l<=r){
    
    
			int mid=l+r>>1;
			if(sum[mid]-sum[last-1]<=x){
    
    
				l=mid+1;
				ans=mid;
			}
			else	r=mid-1;
		}
		last=ans+1;
	}
	return ans[x];
}
 
int main(){
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
    
    
		int num;
		scanf("%d",&num);
		mmax=max(mmax,num);
		sum[i]=sum[i-1]+num;
	}
	scanf("%d",&m);
	while(m--){
    
    
		int x;
		scanf("%d",&x);
		if(x<mmax)	puts("Impossible");
		else		printf("%d\n",solve(x));
	}
}

还有一种方法,就更有点意义不明了。

这种方法明显比二分高效,但不好理解。

1、预处理一个数组rong[maxn],然后把这题理解为以固定步数x走到第n点的意思,rong[i]=j可以理解为已走i长度到达第 j 个数

for(int j=qian[i-1];j<qian[i];j++)
            rong[j]=i-1;

2、前缀和预处理后,从0开始走,每次距离x,走到rong[x]=k(假设是k)后,表示走到第k个点,也意味着这个x中划分了前k个数据,第k个点在rong数组表示一段a[k]长度的距离(可以理解为前k个不受影响后,所以只要考虑从qian[k]开始),所以之后更新res(已走步数),之后再走x,循环下去,不会像二分那样查找失败后再二分,这直接贪心递推下去。

			int l=0,res=0;
            while(l<n){
    
    
                res=min(res+k,maxn);	//防止res超过1e6
                l=rong[res];
                res=qian[l];
                ans[k]++;
//                cout<<l<<' '<<res<<endl;
            }
#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
const int maxn=1e6+7;
const int mod=1e9+7;
int m,n,i,j,maxx=-1;
int a[maxn];
int qian[maxn],rong[maxn],ans[maxn];
int read(){
    
    
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
    
    
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
    
    
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int main(){
    
    
    n=read();
    for(int i=1;i<=n;i++){
    
    
        int x;
        x=read();
        maxx=max(maxx,x);
        qian[i]=qian[i-1]+x;
        for(int j=qian[i-1];j<qian[i];j++)
            rong[j]=i-1;
    }
    for(int i=qian[n];i<=maxn;i++)	rong[i]=n;
//    for(int i=0;i<50;i++)	cout<<rong[i]<<' ';
    int t; t=read();
    while(t--){
    
    
        int k;
        k=read();
        if(k<maxx)	printf("Impossible\n");
        else if(ans[k])	printf("%d\n",ans[k]);
        else{
    
    
            int l=0,res=0;
            while(l<n){
    
    
                res=min(res+k,maxn);	//防止res超过1e6
                l=rong[res];
                res=qian[l];
                ans[k]++;
//                cout<<l<<' '<<res<<endl;
            }
            printf("%d\n",ans[k]);
        }
    }
    return 0;
}

这种方法明显是刷题刷出来的,我也记得以前有一种题也是这样预处理,但不会联系到这题。当然这个方法也要记忆化,(怀疑数据中的m组询问有很多重复的)

猜你喜欢

转载自blog.csdn.net/weixin_45606191/article/details/109005101
今日推荐