牛客编程巅峰赛S1第4场 - 黄金&钻石(题解)
A-牛牛分蛋糕
题意:
牛牛今天家里要来客人,所以牛牛今天特意做了他最拿手的两种蛋糕,但是他是一个有洁癖的人,所以他在分蛋糕时,有如下几个原则:
1.他不希望一个盘子里出现两种蛋糕
2.他希望每个盘子中都有蛋糕
3.他想让装有最少蛋糕数量的盘子中装有的蛋糕数量尽可能多。
输入:
5,2,3
输出:
1
说明:
只有一种方法把蛋糕分配到盘子里,即所有的盘子上都有一个蛋糕。
备注:
n,a,b(1 ≤ a, b ≤ 10^5, 2 ≤ n ≤ a + b)
第一个参数代表盘子的数量
第二个参数代表第一种蛋糕的数量
第三个参数代表第二种蛋糕的数量。
程序应返回:在所有分法中,蛋糕数量最少的盘子中分到最多的蛋糕数量。
思路:
- 二分答案 x x x个最多蛋糕数量
- 假设 x x x个最多蛋糕数量属于第一种蛋糕,设 i i i个盘子装了第一种蛋糕,则总数上满足 i ∗ x ≤ a & & ( n − i ) ∗ x ≤ b i*x \le a \&\& (n-i)*x \le b i∗x≤a&&(n−i)∗x≤b
- 假设 x x x个最多蛋糕数量属于第二种蛋糕,同上
- 二分答案时check这个式子即可
- 复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
Code:
class Solution {
public:
/**
* 处理函数,返回在所有分法中,蛋糕数量最少的盘子中分到最多的蛋糕数量
* @param n int整型 n个盘子
* @param a int整型 a蛋糕数量
* @param b int整型 b蛋糕数量
* @return int整型
*/
bool check(int mid,int n,int a,int b){
for(int i=1;i<=n;++i){
if(i*mid*1ll<=a*1ll&&(n-i)*mid*1ll<=b*1ll){
return 1;
}
if(i*mid*1ll<=b*1ll&&(n-i)*mid*1ll<=a*1ll){
return 1;
}
}
return 0;
}
int solve(int n, int a, int b) {
// write code here
int l=1;
int r=a+b;
int ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid,n,a,b)){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
return ans;
}
};
B-牛牛凑数字
题意:
牛牛今天逛商店,看到商店里摆着一些很漂亮的数字,牛牛非常喜欢,想买一些数字带回家。
数字一共有九种类型,分别是
1
−
9
1-9
1−9这九个数字,每个数字的价钱都不一样,而且每个数字的货源都非常充足。
牛牛是个完美主义者,他希望用自己的能够承受的价格,从这些数字里面购买,并且凑到最大的数字带回家。
输入:
2,[9,11,1,12,5,8,9,10,6]
输出:
"33"
说明:
购买2个第3个数字,可以凑到最大值为33。
备注:
第一个参数为一个整数n(0 ≤ n ≤ 106),代表牛牛所能承受的价格。
第二个参数为1-9这九个数字的价格数组,a1,a2,……,a9(1≤ ai ≤10^5)。
程序应返回:一个数字,代表牛牛能凑到的最大的数字。当然,如果牛牛一个数字都买不起,返回"-1"即可。
注意,由于数字可能会很大,所以程序中需要处理成string类型进行返回。
思路:
CF原题可还行,赛前前一段时间练过,CF349B-Color-the-Fence
- 优先贪最多位数最大值,如果能选最长的有 2222 2222 2222和 3333 3333 3333,很明显要 3333 3333 3333。
- 将处理出来的位数,从高位往底位用剩余的费用优先换最大的可换数,例如 2222 2222 2222,这时可以将第一位换成 3 3 3和 4 4 4,明显换 4 4 4更优,因为受高位影响,如果剩余费用还有 2 2 2,换 3 3 3的费用是 1 1 1,换 4 4 4的费用是 2 2 2,前者结果为 3322 3322 3322,后者结果为 4222 4222 4222,还是后者优,所以贪高位往低位的最大可换数是最优解。
Code:
struct node
{
int v;
int id;
}q[20];
#define dep(i,a,b) for(int i=(a);i>=(b);--i)
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
class Solution {
public:
/**
* 得到牛牛能够凑到的最大的数字
* @param n int整型 牛牛能够承受的价格
* @param a int整型vector 1-9这九个数字的价格数组
* @return string字符串
*/
long long sum[2000005];
string solve(int n, vector<int>& a) {
// write code here
map<long long,long long>mp;
int v;
v=n;
string ss;
if(v==0){
ss="-1";
return ss;
}
for(int i=1;i<=9;++i){
int k;
q[i].v=a[i-1];
q[i].id=i;
mp[i]=a[i-1];
}
int cnt=0;
int num=0;
int maxs=-1;
for(int i=1;i<=9;++i){
if(v/mp[i]>=cnt){
if(maxs<=(v-cnt*mp[i])){
num=i;
cnt=v/mp[i];
maxs=(v-cnt*mp[i]);
}
}
}
//debug(num);
string s;
v-=cnt*mp[num];
rep(i,1,cnt)s+=num+'0';
rep(i,0,s.size()){
dep(j,9,1){
if(v-(mp[j]-mp[s[i]-'0'])>=0&&j>s[i]-'0'){
v-=(mp[j]-mp[s[i]-'0']);
s[i]=j+'0';
break;
}
}
}
if(s.size()==0){
s="-1";
return s;
}
return s;
}
};
C-牛妹的野菜
题意:
书接上回,牛妹组织春游,有一个有趣的项目是挖番薯。聪明的牛妹拿到了一个表明了番薯洞的地图,每个番薯洞中有一定数量的番薯。同时,我们知道番薯洞的连接路径,并规定路径是单向且小序号指向大序号,也无环。可以从任意一处开始挖,然后沿着连接往下挖(仅能选择一条路径),当无连接时,结束。
设计一种挖番薯的方案,使得可以挖到更多的番薯。
输出路径。
输入:
[5,10,20,5,4,5],[[1,2],[1,4],[2,4],[3,4],[4,5],[4,6],[5,6]]
输出:
"3-4-5-6"
说明:
很明显,先去第三点拿20个番薯,再去第四个点拿5个,再去第五个点拿4个,再去第六个点拿5个。这个方案最优
备注:
总番薯数量不超过1000000,番薯洞数量不超过250.
思路:
裸的有向图求最长路路径
- 建图,假设起点为 0 0 0,将 0 0 0与番薯洞 i i i相连,边权为 w [ i ] w[i] w[i](番薯洞的点权)
- 番薯洞的连接路径 l → r l\to r l→r的边权为 w [ r ] w[r] w[r]
- 最后跑源点为 0 0 0的最长路,松弛时记录路径即可
吐槽:Dijstra不能求最长路,Floyd能被卡30%超时,最后写了个SPFA才过掉。
Code:
class Solution {
public:
/**
*
* @param potatoNum int整型vector 依次表示序号为1,2,3..的番薯洞各有多少个番薯
* @param connectRoad int整型vector<vector<>> 每个一维数组[x,y]表示从第x号番薯洞到第y号有路
* @return string字符串
*/
int pa[400000];int w[40000];
int n,m,s,t;
int vis[40000];
int d[40000];
struct node{
int to;
int next;
int dis;
}edge[400000];
int in[400000];
int head[400000],u,v,_d,base;
int num_edge=0;
void edge_add(int from,int to,int dis){
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
edge[num_edge].dis=dis;
head[from]=num_edge;
}
void init(int n){
for(int i=0;i<=0;++i){
for(int j=1;j<=n;++j){
if(i==0){
edge_add(i,j,w[j]);
}
}
}
}
string so(int x){
string ss;
while(x){
ss+=x%10+'0';
x/=10;
}
reverse(ss.begin(),ss.end());
return ss;
}
void spfa(){
//vis[i] 为1表示点i属于S集合 为0表示点i属于T集合
memset(d,-0x3f3f3f3f,sizeof(d));
d[0]=0;
vis[0]=1;
in[0]++;
queue<int>q;
q.push(0);
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
int z=edge[i].dis;
if(d[y]<d[x]+z){//最长路
in[y]++;
d[y]=d[x]+z;
pa[y]=x;//记录路径
if(vis[y]==0){
q.push(y);vis[y]=1;
}
}
}
}
}
string digSum(vector<int>& potatoNum, vector<vector<int> >& connectRoad) {
// write code here
//
int n=potatoNum.size();
int cnt=0;
for(int i=0;i<(int)potatoNum.size();++i){
w[++cnt]=potatoNum[i];
}
init(potatoNum.size());
for(int i=0;i<(int)connectRoad.size();++i){
int l=connectRoad[i][0];
int r=connectRoad[i][1];
edge_add(l,r,w[r]);
}
spfa();
int maxx=0;
int st;
int ed;
for(int i=0;i<=0;++i){
for(int j=0;j<=n;++j){
if(d[j]>maxx){
maxx=d[j];
st=i;
ed=j;
}
}
}
string ans;
std::vector<int>path;
path.push_back(ed);
while(st!=ed){
if(pa[ed]==st){
break;
}
else{
path.push_back(pa[ed]);
}
ed=pa[ed];
}
reverse(path.begin(),path.end());
for(int i=0;i<(int)path.size();++i){
if(i!=(int)path.size()-1){
ans+=so(path[i]);
ans+='-';
}
else ans+=so(path[i]);
}
//cout<<ans;
return ans;
}
};