毒瘤hbx的贪心专题系列题解
A Maximal gcd
题意:现在给定一个正整数 n。你需要找到 k 个严格递增的正整数a1, a2, ..., ak,满足他们的和等于 n 并且他们的最大公因数尽量大。如果不可能请输出 -1。\(1\leq n,k \leq 10^{10}\)
题解:把 n 的所有因子找出来后,求最大因子 x 满足\(x* \frac {k* (k+1)}{2}\leq n\)即可。序列就是\(1* x,2* x,...,(k-1)* x,n-x* \frac{k* (k-1)}{2}\)
注意判断时会爆long long!
复杂度:\(O(\sqrt n)\)
#include<bits/stdc++.h> using namespace std; #define ll long long ll n,k,tmp,tp,sum,fac[5005]; int faccnt; inline bool check(ll x) { return x<=(2*n/k)/(k+1);//!!! } int main() { scanf("%lld%lld",&n,&k); sum=n; if(!check(1)) { puts("-1"); return 0; } for(register ll i=1;i*i<=n;++i) { if(n%i) continue; if(check(i)) tmp=max(tmp,i); if(check(n/i)) tmp=max(tmp,n/i); } for(int i=1;i<k;++i) printf("%lld ",1ll*i*tmp),sum-=i*tmp; printf("%lld\n",sum); return 0; }
B Arthur and walls
题意:给出一个n* m的矩阵,里面有“*”和“.”两种符号,要求把最少的“*”变成“.”,使得“.”的联通块构成一个矩形。求最少需要变几个“*”。\(1\leq n,m \leq 2000\)
题解:如果某个2* 2的方块里只有一个“*”则把这个“*”变掉。bfs即可。
复杂度:\(O(n^2)\)
#include<bits/stdc++.h> using namespace std; #define maxn 2005 int G[maxn][maxn],b[5]; char s[maxn][maxn]; int n,m; struct Node { int x,y; Node(){} Node(int a,int b):x(a),y(b){} }; queue<Node> q; inline void work(int x,int y) { if(G[x][y]) G[x][y]=0,q.push(Node(x,y)); } inline void bfs() { while(!q.empty()) { int x=q.front().x,y=q.front().y; q.pop(); b[1]=b[2]=b[3]=b[4]=0; if(x!=1&&y!=1) b[1]=G[x-1][y-1]+G[x-1][y]+G[x][y-1]; if(x!=1&&y!=m) b[2]=G[x-1][y]+G[x-1][y+1]+G[x][y+1]; if(x!=n&&y!=1) b[3]=G[x][y-1]+G[x+1][y-1]+G[x+1][y]; if(x!=n&&y!=m) b[4]=G[x][y+1]+G[x+1][y]+G[x+1][y+1]; if(b[1]==1) work(x-1,y-1),work(x-1,y),work(x,y-1); if(b[2]==1) work(x-1,y),work(x-1,y+1),work(x,y+1); if(b[3]==1) work(x,y-1),work(x+1,y-1),work(x+1,y); if(b[4]==1) work(x,y+1),work(x+1,y),work(x+1,y+1); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) { scanf("%s",s[i]+1); for(int j=1;j<=m;++j) { if(s[i][j]=='.') q.push(Node(i,j)); else G[i][j]=1; } } bfs(); for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { if(G[i][j]==1) cout<<"*"; else cout<<"."; } puts(" "); } return 0; }
C Student's Revenge
题意:有一个主席每天要处理繁重的事务,但学生们仍然对他非常反感。现在有 n 个任务,学生们会选出 p 个,在这其中主席会选出 k 个来完成,剩下的 n−k 个不会完成。
完成一项任务会使主席的头发变白\(a_{i}\),不完成就会使学生们的不满意度增加\(b_{i}\)。主席会使\(\sum{b_{i}}\)尽量小,在此基础上使\(\sum{a_{i}}尽量小\)。但是学生们想要使\(\sum{a_{i}}\)尽量大,在此基础上使\(\sum{b_{i}}\)尽量大。
求学生会选哪 p 个以及主席会选哪 k 个。
输出时先输出学生选且主席不选的 p-k 个,再输出主席选了的 k 个。按编号大小顺序输出。
\(1\leq k \leq p \leq n \leq 10^5,1\leq a_{i},b_{i}\leq 10^9\)
题解:若给定 p 个任务,则主席必然先按 b 降序再按 a 升序排序依次解决。所以反推一下先按 b 升序再按 a 降序的前 p-k 个肯定不会选上。所以先把 n 个中的前 p-k 个去掉(标记不选即可)。
其次我们要选 k 个 a 最大的和 p-k 个 b 最大的。为了让主席只选那k个,必须有前k个的b值都大于后面p-k个的b值。具体实现看代码吧。
复杂度:\(O(n\log n)\)
#include<cstdio> #include<algorithm> using namespace std; #define maxn 100005 struct Things { int a,b,id,state; }thi[maxn]; int n,p,k,vis[maxn],cnt,num; inline bool cmp1(Things x1,Things x2) { if(x1.b!=x2.b) return x1.b<x2.b; if(x1.a!=x2.a) return x1.a>x2.a; return x1.state<x2.state; } inline bool cmp2(Things x1,Things x2) { if(x1.a!=x2.a) return x1.a>x2.a; return x1.b>x2.b; } int main() { scanf("%d%d%d",&n,&p,&k); for(int i=1;i<=n;++i) scanf("%d%d",&thi[i].a,&thi[i].b),thi[i].id=i; sort(thi+1,thi+n+1,cmp1); for(int i=1;i<=p-k;++i) thi[i].state=1;//扔后面去 sort(thi+1,thi+n+1,cmp2); for(int i=1;cnt<k;++i) if(!thi[i].state) { ++cnt; vis[thi[i].id]=1; thi[i].state=2;//同上 printf("%d ",thi[i].id); } sort(thi+1,thi+n+1,cmp1); cnt=0; for(int i=n;num<p-k;--i)//从后往前扫 { if(cnt>=k) { ++num; printf("%d ",thi[i].id); } if(vis[thi[i].id]) ++cnt; } return 0; }
D Ball coloring
题意:有n个数对,可以将数对中的两个数分别放入 l 堆和 r 堆中。求\(min((R_{max}-R_{min})* (L_{max}-L_{min}))\)
\(1\leq n \leq 200000,1\leq num\leq 10^9\)
题解:设\(Min,Max\)为全局最大(小)值。
1):若Min,Max不在同一堆中:把所有较小数丢到Min堆,较大数丢入Max堆。
2):若在同一堆中:我们需求出\(min(R_{max}-R_{min})\)。令x为数对较小值,y为数对较大值。按x排序。依次交换x,y并更新答案。
注意:如果Min,Max在同一数对中可跳过2)。
复杂度:\(O(n\log n)\)
#include<bits/stdc++.h> using namespace std; #define maxn 200005 struct Card { int x,y; inline friend bool operator < (Card a,Card b) { if(a.x==b.x) return a.y<b.y; return a.x<b.x; } }card[maxn]; int Rmax,Rmin,Bmax,Bmin,maxpos,minpos; int main() { int n; scanf("%d",&n); if(n==1) { puts("0"); return 0; } for(int i=1;i<=n;++i) { scanf("%d%d",&card[i].x,&card[i].y); if(card[i].x>card[i].y) swap(card[i].x,card[i].y); } sort(card+1,card+n+1); int maxy=card[1].y,minx=card[1].x; for(int i=1;i<=n;++i) { if(card[i].x<=minx&&card[i].y>=maxy) { maxpos=minpos=i; minx=card[i].x,maxy=card[i].y; } else if(card[i].x<minx) { minpos=i; minx=card[i].x; } else if(card[i].y>maxy) { maxpos=i; maxy=card[i].y; } } Rmax=maxy,Bmin=minx;//全局最大最小 Rmin=card[minpos].y,Bmax=card[maxpos].x; for(int i=1;i<=n;++i) { Rmin=min(Rmin,card[i].y); Bmax=max(Bmax,card[i].x); } long long ans=1ll*(Rmax-Rmin)*(Bmax-Bmin);//第一种情况 if(maxpos!=minpos)//第二种情况 { Bmax=maxy,Bmin=minx; Rmax=max(card[n].x,card[1].y); int premin=card[1].y; Rmin=min(card[1].y,card[2].x); int tmp=Rmax-Rmin; for(int i=2;i<n;++i) { Rmax=max(card[i].y,Rmax); Rmin=min(min(card[i].y,premin),card[i+1].x); premin=min(premin,card[i].y); if(Rmax-Rmin<tmp) tmp=Rmax-Rmin; } ans=min(ans,1ll*tmp*(Bmax-Bmin)); } printf("%lld\n",ans); return 0; }
E Ant man
题意:有 n 个点,每个点有 x,a,b,c,d 五个属性。从 i 到 j 的距离定义为
\begin{cases}
\mid x_{i}-x_{j} \mid+c_{i}+b_{j}, & x_{i}>x_{j} \newline
\mid x_{i}-x_{j} \mid+d_{i}+a_{j}, & x_{j}>x_{i}
\end{cases}
给定起点终点,求经过每个点恰好一次的最短路。
\(n\leq 5000,1\leq x,a,b,c,d \leq 10^9\)
保证 x,a,b,c,d 都是严格单调递增的。
题解:直接在区间中插入,维护一个链表。
复杂度:\(O(n^2)\)
#include<bits/stdc++.h> using namespace std; struct Point { long long x,a,b,c,d; }poi[5005]; int n,s,e,nex[5005],tmppos; long long ans,cost,tmpcost; inline long long dist(int x,int y) { Point a=poi[x],b=poi[y]; if(b.x<a.x) return a.x-b.x+a.c+b.b; return b.x-a.x+a.d+b.a; } int main() { scanf("%d%d%d",&n,&s,&e); for(int i=1;i<=n;++i) scanf("%lld",&poi[i].x); for(int i=1;i<=n;++i) scanf("%lld",&poi[i].a); for(int i=1;i<=n;++i) scanf("%lld",&poi[i].b); for(int i=1;i<=n;++i) scanf("%lld",&poi[i].c); for(int i=1;i<=n;++i) scanf("%lld",&poi[i].d); nex[s]=e; ans+=dist(s,e); for(int i=1;i<=n;++i) { if(i==s||i==e) continue; cost=1e18; for(int j=s;j!=e;j=nex[j]) { tmpcost=dist(j,i)+dist(i,nex[j])-dist(j,nex[j]); if(cost>tmpcost) { cost=tmpcost; tmppos=j; } } ans+=cost; nex[i]=nex[tmppos]; nex[tmppos]=i; } printf("%lld\n",ans); return 0; }
以上为考试题目。
F New Year Snowman
题意:给定\(r_{i}\),求元素互不相同的三元组最多有多少个。\(1\leq n\leq 5* 10^5,r_{i} \leq 10^6\)
题解:每次把剩余最多的三个拿出来即可。
复杂度:\(O(n\log n)\)
#include<bits/stdc++.h> using namespace std; #define maxn 100005 int n,cnt,tmp[maxn],anscnt,t1,t2,t3; struct Ball { int size,num; inline friend bool operator < (Ball a,Ball b) { if(a.num==b.num) return a.size<b.size; return a.num<b.num; } }ball[maxn],tmpa,tmpb,tmpc; struct Ans { int t1,t2,t3; }ans[maxn]; priority_queue<Ball> q; inline void func(Ball x) { if(x.num>1) q.push((Ball){x.size,--x.num}); else --cnt; } inline void work() { tmpa=q.top(),q.pop(); tmpb=q.top(),q.pop(); tmpc=q.top(),q.pop(); t1=tmpa.size,t2=tmpb.size,t3=tmpc.size; if(t1<t2) swap(t1,t2); if(t1<t3) swap(t1,t3); if(t2<t3) swap(t2,t3); ans[++anscnt]=(Ans){t1,t2,t3}; func(tmpa),func(tmpb),func(tmpc); } int main() { int n; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&tmp[i]); sort(tmp+1,tmp+n+1); for(int i=1;i<=n;++i) { if(tmp[i]!=tmp[i-1]) ball[++cnt].size=tmp[i],ball[cnt].num=1; else ++ball[cnt].num; } for(int i=1;i<=cnt;++i) q.push(ball[i]); while(cnt>=3) work(); printf("%d\n",anscnt); for(int i=1;i<=anscnt;++i) printf("%d %d %d\n",ans[i].t1,ans[i].t2,ans[i].t3); return 0; }
G 南园满地堆轻絮
题意:对于正整数数列\(a_{i}\)和\(b_{i}\), 且b序列非严格单调递增, 使得\(max(\mid a_{i}-b_{i}\mid)\)尽量小。给定\(a_{i}\)。
\(n\leq 5* 10^6\)
题解:画一个\(a_{i}\)折线图可以发现答案为最大的逆序对的差除二向上取整。当然二分也可以(虽然我不知道为什么带了一个log跑的比没带log还快一些)。
复杂度:\(O(n)\)
#include<bits/stdc++.h> using namespace std; long long a[5000005],sa,sb,sc,sd,mod; int n; inline long long mul(long long a,long long b) { return a*b%mod; } inline long long func(long long x) { return (mul(sa,mul(x,mul(x,x)))+mul(sb,mul(x,x))+mul(sc,x)+sd)%mod; } inline void pre()//生成数据的 { scanf("%d%lld%lld%lld%lld%lld%lld",&n,&sa,&sb,&sc,&sd,&a[1],&mod); for(int i=2;i<=n;++i) a[i]=(func(a[i-1])+func(a[i-2]))%mod; } int main() { pre(); long long ans=-1e18,maxn=-1e18; for(int i=1;i<=n;++i) { if(maxn>a[i]) ans=max(ans,(maxn-a[i]+1)>>1); else maxn=a[i]; } printf("%lld\n",ans); return 0; }
H Pavel and Triangles
题意:有 n 种木棒,第 i 种的长度是\(2^{i-1}\)。给定每种木棒的数量\(a_{i}\),求最多能组成多少个三角形。
\(1\leq n\leq 3* 10^5,a_{i}\leq 10^9\)
题解:从前往后扫,优先组等腰,还有多就组等边。
复杂度:\(O(n)\)
#include<bits/stdc++.h> using namespace std; #define ll long long ll a[300005]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%lld",&a[i]); ll ans=a[1]/3,num; a[1]%=3; ll res=a[1]; for(int i=2;i<=n;++i) { num=min(res,a[i]/2); res-=num; ans+=num; a[i]-=num*2; ans+=a[i]/3; res+=a[i]%3; } printf("%lld",ans); return 0; }
I Nauuo and Cards
题意:你的手上有 n 张牌,牌堆中有 n 张牌,共有 n 张 0 牌,和 n 张数字牌(从 1 到 n )。定义一次操作为将手中的牌放到牌堆的底部,并把牌堆顶端的牌拿到手中,求使用最少的操作次数能够让牌堆中的牌变为\(1\sim n\)的有序序列。
题解:如果1号牌在手上,则直接一个个先收再打即可。每张牌位置为\(pos_{i}\),代价为\((pos[i]+1)+(n-i)\),也就是收牌加打出牌。取max即可。如果一号牌在牌堆里,则看能否形成从\(pos[1]\sim n\),\(1\sim k\)的序列。如果有这样的序列则显然更优。
复杂度:\(O(n)\)
#include<cstdio> using namespace std; #define maxn 200005 int a[maxn], b[maxn], pos[maxn], ans; inline int max(int a, int b) { return a > b ? a : b; } int main() { int n, i; scanf("%d", &n); for (i = 1; i <= n; ++i) scanf("%d", &a[i]), pos[a[i]] = 0; for (i = 1; i <= n; ++i) scanf("%d", &b[i]), pos[b[i]] = i; for (i = 1; i <= n; ++i) ans = max(ans, pos[i] - i + n + 1); if (pos[1]) { for (i = 2; pos[i] == pos[1] + i - 1; ++i); if (pos[i - 1] == n) { int j; for (j = i; j <= n && pos[j] <= j - i; ++j); if (j > n) { printf("%d\n", n + 1 - i); return 0; } } } printf("%d\n", ans); return 0; }
J Match Points
题意:给定数列\(x_{i}\),若\(z\leq \mid x_{i}-x_{j}\mid\),则称\(x_{i},x_{j}\)能匹配上。
求最多能配对多少组。\(2\leq n\leq 2* 10^5,1\leq z,x_{i}\leq10^9\)
题解:二分。前mid个和后mid个依次匹配即可。
复杂度:\(O(n\log n)\)
#include<bits/stdc++.h> using namespace std; #define maxn 200005 int n,z,pos[maxn]; inline bool check(int x) { for(int i=1;i<=x;++i) if(abs(pos[i]-pos[n-x+i])<z) return false; return true; } int main() { scanf("%d%d",&n,&z); for(int i=1;i<=n;++i) scanf("%d",&pos[i]); sort(pos+1,pos+n+1); int l=0,r=(n>>1),mid,ans; while(l<=r) { mid=(l+r)>>1; if(check(mid)) l=mid+1,ans=mid; else r=mid-1; } printf("%d\n",ans); return 0; }
K Artem and Array
题意:给定序列\(a_{i}\),每删去一个数可获得左边一个的数与右边一个的数的最小值的分数。求分数最大值。\(1\leq n\leq 5* 10^5,1\leq a_{i}\leq 10^6\)
题解:先删去中间小两边大的(单调栈),在排序删去除最后两个之外的。
复杂度:\(O(n\log n)\)
#include<bits/stdc++.h> using namespace std; #define ll long long ll a[600005], ans; int n, top, x; int main() { scanf("%d", &n); while (n--) { scanf("%d", &x); while (top && a[top] <= x && a[top] <= a[top - 1]) ans += min(a[--top], x * 1ll); a[++top] = x; } sort(a + 1, a + top + 1); for (int i = 1; i <= top - 2; ++i) ans += a[i]; printf("%lld", ans); return 0; }
L Beijing Guards
题意:n 个人形成环。每个人所收到的礼品和他两旁的人都不能有一样的。问最多需要准备多少种礼品。每个人需要\(a_{i}\)个礼品。\(1\leq n\leq 10^5\)
题解:如果 n 为偶数则直接取\(max(a_{i}+a_{i+1})\)即可。否则二分。设\(a_{1}\)为左右分界线。若 i 为奇数则尽量往左取否则往右取。最后看第 n 个是否在左边取过即可。
注意 n=1 时要直接输出。
复杂度:\(O(n\log n)\)
#include<cstdio> #include<algorithm> using namespace std; #define maxn 100005 int n, a[maxn], cl[maxn], cr[maxn]; long long l, r; inline bool check(long long x) { cl[1] = a[1]; for (int i = 2; i <= n; ++i) { if (i & 1) { cr[i] = min(0ll + a[i], x - a[1] - cr[i - 1]); cl[i] = a[i] - cr[i]; } else { cl[i] = min(a[i], a[1] - cl[i - 1]); cr[i] = a[i] - cl[i]; } } return !cl[n]; } int main() { while (scanf("%d", &n) && n) { l = r = 0; for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), r += a[i]; if (n == 1) { printf("%d\n", a[1]); continue; } a[n + 1] = a[1]; for (int i = 1; i <= n; ++i) l = max(l, 0ll + a[i] + a[i + 1]); if (!(n & 1)) { printf("%lld\n", l); continue; } while (l <= r) { long long mid = (l + r) >> 1; if (check(mid)) r = mid - 1; else l = mid + 1; } printf("%lld\n", l); } return 0; }
M Moving Tables
题意:有一条走廊,每次只能通过一张桌子。在任意两个房间间搬桌子都要10分钟。给定n个桌子以及起点终点,求最多要多少分钟搬完。同一时刻可以搬多张桌子。400间房间,\(n\leq 200\)
题解:将搬动区间加10再最后取max。
复杂度:\(O(n^2)\)
#include<bits/stdc++.h> using namespace std; int cnt[405]; int main() { int t, n, st, ed, ans; scanf("%d", &t); while (t--) { scanf("%d", &n); memset(cnt, 0, sizeof(cnt)); while (n--) { scanf("%d%d", &st, &ed); if (st > ed) swap(st, ed); if (!(st & 1)) --st; if (ed & 1) ++ed; for (int i = st; i <= ed; ++i) cnt[i] += 10; } ans = 0; for (int i = 1; i <= 400; ++i) ans = max(ans, cnt[i]); printf("%d\n", ans); } return 0; }