Write by Jalan
题干
火星人用一串钻石链付款,钻石链切下来之后不能复原
给你一串钻石链上面是他们的价值代码,和一个价格,你需要找出所有的支付方法,如果没有可能准确支付的时候找花费最小的方法。
输入格式
第一行两个数一个N<=105是钻石总数M<=108顾客需要支付的数额
下一行给N个数字每个<=10^3是钻石的价值用空格分开,下标是1-N
输出格式
保证钻石的总价值对于支付是充足的,如果能准确支付,打印i-j角标,多个的时候按i升序.不能准确支付打印使超出最小的i-j也是按i升序.
题解
第一次(2,5测试点超时)
思路
对整个串的每个元素进行循环.
保存现有的最小差值和每个元素进行循环时的和,以及输出存档.
如果和和价值M的差值比现有最小差值小,那么清空输出存档,向输出存档里加入这次的数据.
如果和和价值M的差值和现有最小差值相等,那么向输出存档里加入这次的数据.
如果和和价值M的差值比现有最小差值大,什么都不做,结束循环.
预期时间复杂度
n^2
代码
C++
#include <cstdint>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include<cstdio>
#include <iostream>
typedef struct pairr
{
int i;
int j;
}pairr;
using namespace std;
int main(int argc, char const *argv[])
{
//input
int N,M;
scanf("%d %d",&N,&M);
int *chain=(int *)malloc(sizeof(int)*N+5);
for (int i = 1; i <= N; i++)
{
scanf("%d",chain+i);
}
//process
int min = INT32_MAX;
vector<pairr> out;
int this_turn_count=0;
for (int k = 1; k <=N; k++)
{
this_turn_count=0;
for (int l = k; l <= N; l++)
{
this_turn_count+=chain[l];
if (this_turn_count>=M)//足够支付的情况下
{
while (this_turn_count-chain[k]>=M)
{
this_turn_count-=chain[k];
k++;
}//小优化,把前面可以裁掉的部分裁掉.
if (this_turn_count-M<min)//比当前差比最小差小的时候清空存档加入当前,循环终止
{
out.clear();
min = this_turn_count - M;
out.push_back({k, l});
break;
}
if (this_turn_count-M==min)//当前差等于最小差的时候当前对加入存档,循环中缀
{
out.push_back({k, l});
break;
}
//比当前差比最小差大的时候什么都不做,循环终止
break;
}
}
}
//output
for (int i = 0; i < out.size(); i++)
{
printf("%d-%d\n", out[i].i, out[i].j);
}
return 0;
}
第二次(全过)
思路
在第一次的前提下做优化吧,我这里主要想到了两种优化方案,一个是把算法变成在线的,但是通过这种方法还是超时了,另一个是加入二分把算法简化,这里写的是加入二分的版本
用一个数组chainSum储存链的有序和用于后面的二分查找,显然的,链中从i位置到j位置的价值和就是chainSum[j]-chainSum[i-1];
预期时间复杂度
nlogn
代码
CPP
//火星人用一串钻石链付款,钻石链切下来之后不能复原
//给你一串钻石链上面是他们的价值代码,和一个价格,你需要找出所有的支付方法,如果没有可能准确支付的时候找花费最小的方法。
//第一行两个数一个N<=10^5是钻石总数M<=10^8顾客需要支付的数额
//下一行给N个数字每个<=10^3是钻石的价值用空格分开,下标是1-N
//保证钻石的总价值对于支付是充足的,如果能准确支付,打印i-j角标,多个的时候按i升序.不能准确支付打印使超出最小的i-j也是按i升序.
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
typedef struct pairr
{
int i;
int j;
} pairr;
using namespace std;
int main(int argc, char const *argv[])
{
//input
int N, M;
scanf("%d %d", &N, &M);
int *chainSum = (int *)malloc(sizeof(int) * N + 5);
int temporary;
chainSum[0] = 0;
for (int i = 1; i <= N; i++)
{
scanf("%d", &temporary);
chainSum[i] = chainSum[i - 1] + temporary;
}
//用chainSum储存递增的链和用于二分查找.
//process
int mindifference = INT32_MAX;
vector<pairr> result;
for (int k = 1; k <= N; k++)
{
//二分查找
int left = k, right = N;
int mid;
int j; //用于存放二分到的右端,左端固定是k.
while (left < right)
{
mid = (left + right)/2;
if (chainSum[mid] - chainSum[k - 1] >= M)
{
right = mid;
}
else
{
left = mid + 1;
}
}
j = right;
while (chainSum[j] - chainSum[k] > M) //把前面可能多出的小价值除去.
{
k++;
}
int paidValue = chainSum[j] - chainSum[k - 1]; //差额
if (paidValue>=M)
{
//差额比最小差小的时候清空result数组,加入当前组
//相等的时候加入当前组
//大的时候什么都不做
int difference=paidValue-M;
if (difference < mindifference)
{
mindifference = difference;
result.clear();
result.push_back({k, j});
}
else if (difference == mindifference)
{
result.push_back({k, j});
}
}
}
//output
for (int i = 0; i < result.size(); i++)
{
printf("%d-%d\n", result[i].i, result[i].j);
}
return 0;
}
运行用时
看在我写了这么多注释的份上可以给我点个赞嘛,求求惹=]砰砰砰,给我加点写下去的油呀@.@