1、汽艇(Boat.cpp/c/pas)
【问题描述】
有 n 个人要去坐 1 汽艇,每个人单独坐汽艇的快乐程度是 Ci,每多一个人,他的快乐程度会减去 Di,请求出使快乐程度之和达到最大的方案。(假设汽艇的容量足够大)。
【输入格式】
输入文件共有 3 行:
第1 行是一个整数 n;
第2 行有n 个整数,依次表示每个人单独坐汽艇的快乐程度 Ci(1<=Ci<=10000);
第3 行有n 个整数,依次表示每多 1 人,每个人快乐程度的下降值 Di(1<=Di<=10)。
【输出格式】
应输出两行:
第1 行一个整数,是最大的快乐程度之和;
第2 行一个整数,是最大的快乐程度之和所对应的汽艇上的人数(若有多种方案,则输出人数最多的)。
【输入样例】
6
10 10 10 10 10 9
2 2 2 2 2 3
【输出样例】
18
3
【样例说明】
前 3 个人去坐汽艇可使快乐程度之和达到最大,每个人的快乐程度均为 10-2*2=6,总和是
18。
【数据范围】
对于 30%的数据,n<=20;
对于 100%的数据,n<=1000。
正解:贪心
解题报告:
标准水题,枚举有多少个人,然后每次sort,取最优的情况即可。
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 1011; 21 int n,ans,cnt,jilu; 22 23 struct node{ 24 int val; 25 int fall; 26 }a[MAXN]; 27 28 struct now{ 29 int val; 30 }jump[MAXN]; 31 32 inline int getint() 33 { 34 int w=0,q=0; 35 char c=getchar(); 36 while((c<'0' || c>'9') && c!='-') c=getchar(); 37 if (c=='-') q=1, c=getchar(); 38 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 39 return q ? -w : w; 40 } 41 42 inline bool cmp(node q,node qq){ return q.val>qq.val; } 43 44 inline bool ccmp(now q,now qq){ return q.val>qq.val; } 45 46 inline void solve(){ 47 n=getint(); 48 49 for(int i=1;i<=n;i++) a[i].val=getint(); 50 51 for(int i=1;i<=n;i++) a[i].fall=getint(); 52 53 sort(a+1,a+n+1,cmp); ans=a[1].val; jilu=1; 54 for(int i=2;i<=n;i++) { 55 for(int j=1;j<=n;j++) jump[j].val=a[j].val-(i-1)*a[j].fall; 56 sort(jump+1,jump+n+1,ccmp); 57 cnt=0; 58 for(int j=1;j<=i;j++) cnt+=jump[j].val; 59 if(cnt>=ans) { 60 ans=cnt; 61 jilu=i; 62 } 63 } 64 printf("%d %d",ans,jilu); 65 } 66 67 int main() 68 { 69 solve(); 70 return 0; 71 }
2、上网(net.cpp/c/pas)
【问题描述】
假设有 n 个人要上网,却只有 1 台电脑可以上网。上网的时间是从 1 szw 至 T szw ,szw是一个自创的时间单位,至于 szw怎么换算成 s,min 或 h,没有人清楚。依次给出每个人在某个时间段内上网的快乐程度 C(必须这个人在整个时间段内都在上网,才能获得快乐程度C,否则,快乐程度是 0),请你得到使总的快乐程度达到最大的方案。
【输入格式】
第1 行2 个整数 n和 T,含义如题目所述;
接下来有 n 个这样的结构(每两个相邻的结构之间有一空行,且第 1 个结构和第一行间有
一空行):
第1 行一个整数 Mi,表示第 i 个人的时间段的个数;
接下来有 Mi 行,每行3个整数 Xj,Yj,C,表示第 i个人在[Xj,Yj]内上网的快乐程度为 C,
因此有 Xj-Yj-1=1,X1=1,Ymi=T,Xj<=Yj。
【输出格式】
仅输出一行,为总的最大的快乐程度。
【输入样例】
3 10
3
1 3 6
4 7 9
8 10 3
3
1 3 5
4 7 10
8 10 1
4
1 3 2
4 8 2
9 9 6
10 10 3
【输出样例】
25
【样例说明】
在[1,3]内,安排 1 上网,快乐程度为 6;
在[4,7]内,安排 2 上网,快乐程度为 10; 在[8,8]内,不安排;
在[9,9]内,安排 3 上网,快乐程度为 6;
在[10,10]内,安排 3 上网,快乐程度为 3;
这是使总的快乐程度达到最大的方案,对应的值是 25。
【数据范围】
对于 30%的数据,n<=4,所有的 Mi<=5,T<=20;
对于 60%的数据,n<=100,所有的 Mi<=100,T<=2000;
对于 100%的数据,n<=500,所有的 Mi<=500,T<=500000,所有的 0<C<=10^9,并保证最终解 Max<=10^9。
正解:DP
解题报告:
这道题我开始居然想了很久,我真是太菜了。。。
果断DP,每个人的各段区间没有任何区别,直接丢一起按照左端点排序。题目可转化为给定若干带权区间,取出不重叠的区间使权值最大。
貌似大家都是在做区间数的做法。。。我是直接讨论时间,for时间,然后记录一下处理到哪段区间,并且当前的最左端点,一维DP
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 511; 21 const int MAXT = 500011; 22 int n,tim; 23 int m[MAXN]; 24 int cnt; 25 int f[MAXT];//f[i]表示i时刻(含)之前的最大值 26 27 struct seq{ 28 int l,r,val; 29 }a[MAXN*MAXN],jump[MAXN*MAXN]; 30 31 inline int getint() 32 { 33 int w=0,q=0; 34 char c=getchar(); 35 while((c<'0' || c>'9') && c!='-') c=getchar(); 36 if (c=='-') q=1, c=getchar(); 37 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 38 return q ? -w : w; 39 } 40 41 inline bool cmp(seq q,seq qq){ if(q.l==qq.l) return q.r<qq.r; return q.l<qq.l; } 42 43 int main() 44 { 45 n=getint(); tim=getint(); 46 for(int i=1;i<=n;i++) { 47 m[i]=getint(); 48 for(int j=1;j<=m[i];j++) { 49 a[++cnt].l=getint(); 50 a[cnt].r=getint(); 51 a[cnt].val=getint(); 52 } 53 } 54 55 sort(a+1,a+cnt+1,cmp); 56 57 int ji=1,nowl=a[1].l; 58 //f[a[1].r]=a[1].val; 59 for(int i=1;i<=tim;i++) { 60 f[i]=max(f[i-1],f[i]); 61 if(i<nowl) continue; 62 while(a[ji].l==nowl && ji<=cnt) f[a[ji].r]=max(f[a[ji].r],f[i-1]+a[ji].val),ji++; 63 nowl=a[ji].l; 64 } 65 66 printf("%d",f[tim]); 67 return 0; 68 }
3、集合(gather)
【问题描述】
小明总喜欢做一些与众不同的事情,最近他迷上了集合运算。他觉得光是简单的预算不过瘾,所以他把2个集合运算集合起来。由于小明很喜欢区间运算。于是他定义了一个这样的值。
你N个各不相同的区间,请你从中找出若干个区间使其权值最大。
W=|A1∪A2∪……∪AK|*|A1∩A2∩……AK|
其中{A1,A2……AK}(K>1,Ai<>Aj{i<>j})
如果这些区间没有交集则权值为0,给你N个各不相同的区间,请你从中找出若干个区间使其权值最大,并告诉小明。
【输入文件】
第一行N
接下来N行 l r(1<=l<r<=10^6)
【输出文件】
最大权值
【输入样例】
4
1 6
4 8
2 7
3 5
【输出样例】
24
【数据约定】
10% N<=10
30% N<=2*10^4
50% N<=10^5
100% 1<N<=10^6
正解:决策单调性
解题报告:
考场上明明想出来了,一定是两个的时候最优,但是自己不相信自己,而且感觉n^2的暴力也会TLE就没打,打了一个大暴力,水到20分。
等我打完大暴力,心血来潮make数据发现永远是2个的时候最优。。。追悔莫及。。。
显然直接两两枚举会TLE,就要考虑优化。
题解说 用决策单调性,有一些奇奇怪怪的性质。传送门:http://www.cnblogs.com/chenyushuo/p/5261657.html
这道题居然是BZOJ原题,出题人真懒。
先贴一发大暴力:
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 1000011; 21 int n; 22 bool pd[MAXN]; 23 int ans; 24 25 struct seq{ 26 int l,r; 27 }a[MAXN],dui[MAXN]; 28 29 inline int getint() 30 { 31 int w=0,q=0; 32 char c=getchar(); 33 while((c<'0' || c>'9') && c!='-') c=getchar(); 34 if (c=='-') q=1, c=getchar(); 35 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 36 return q ? -w : w; 37 } 38 39 inline bool cmp(seq q,seq qq){ 40 if(q.l==qq.l) return q.r<qq.r; 41 return q.l<qq.l; 42 } 43 44 inline void check(int cnt){ 45 if(cnt==0 || cnt==1) return ; 46 int total=0; 47 for(int i=1;i<=n;i++) if(pd[i]) dui[++total]=a[i]; 48 sort(dui+1,dui+total+1,cmp); 49 int head=dui[1].l,tail=dui[1].r; 50 int maxl=0; 51 for(int i=1;i<=total;i++) head=max(head,dui[i].l),tail=min(tail,dui[i].r); 52 if(head>=tail) return; 53 maxl=tail-head; 54 55 int nowl=dui[1].l,nowr=dui[1].r; 56 int ji=0; ji=nowr-nowl; 57 for(int i=2;i<=total;i++) { 58 if(dui[i].l==nowl) ji+=dui[i].r-nowr,nowr=dui[i].r; 59 else{ 60 nowl=dui[i].l; 61 if(nowl>nowr) { 62 ji+=dui[i].r-dui[i].l; 63 nowr=dui[i].r; 64 } 65 else{ 66 if(dui[i].r>nowr) { 67 ji+=dui[i].r-nowr; 68 nowr=dui[i].r; 69 } 70 } 71 } 72 } 73 maxl*=ji; 74 if(maxl>ans) { 75 ans=maxl; 76 /*for(int i=1;i<=n;i++) 77 if(pd[i]) 78 printf("%d ",i); 79 printf(" %d ",ans);*/ 80 } 81 } 82 83 inline void dfs(int now,int cnt){ 84 if(now==n+1) { check(cnt);return ; } 85 pd[now]=1; dfs(now+1,cnt+1); 86 pd[now]=0; dfs(now+1,cnt); 87 } 88 89 inline void solve(){ 90 n=getint(); 91 for(int i=1;i<=n;i++) a[i].l=getint(),a[i].r=getint(); 92 93 dfs(1,0); 94 95 printf("%d",ans); 96 } 97 98 int main() 99 { 100 solve(); 101 return 0; 102 }
正解:单调队列+单调栈
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 1000011; 21 int n,cnt; 22 LL ans; 23 24 struct seq{ 25 int l,r; 26 }a[MAXN]; 27 28 inline LL calc(const seq &j,const seq &i){return 1LL*(i.r-j.l)*(j.r-i.l);} 29 30 inline int getint() 31 { 32 int w=0,q=0; 33 char c=getchar(); 34 while((c<'0' || c>'9') && c!='-') c=getchar(); 35 if (c=='-') q=1, c=getchar(); 36 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 37 return q ? -w : w; 38 } 39 40 inline bool cmp(seq q,seq qq){ 41 if(q.l==qq.l) return q.r>qq.r; 42 return q.l<qq.l; 43 } 44 45 inline LL max(LL pp,LL ppp){ if(pp<ppp) return ppp; return pp; } 46 47 struct stac{ 48 int pos,top; 49 struct data{ 50 int l,r,id; 51 }lin,sta[MAXN]; 52 inline void init(){ sta[top=1]=(data){2,n,1}; pos=1; } 53 bool comp(int xx,int x,int y){return calc(a[x],a[xx])<calc(a[y],a[xx]);} 54 inline int get(int id){ 55 int l=lin.l,r=lin.r,bian=lin.id; 56 int mid; 57 while(l<r) { 58 mid=(l+r)/2+1; 59 if(comp(mid,bian,id)) l=mid; else r=mid-1; 60 } 61 return l; 62 } 63 inline void push(int id){ 64 while(top && !comp(sta[top].l,sta[top].id,id)) top--; 65 lin=sta[top--]; 66 int m=get(id); 67 if(lin.l<=m) sta[++top]=(data){lin.l,m,lin.id}; 68 if(m<n) sta[++top]=(data){m+1,n,id}; 69 } 70 inline LL query(int x){ 71 while(x>sta[pos].r) pos++; 72 return calc(a[sta[pos].id],a[x]); 73 } 74 }stack; 75 76 struct queue{//单调队列,维护一个左端点、右端点坐标都单调递增,长度单调递减的区间队列 77 int head,tail; 78 struct data{ 79 int r,len; 80 }seq[MAXN],lin; 81 inline void push(int id){ 82 lin=(data){a[id].r,a[id].r-a[id].l}; 83 while(head<=tail && seq[tail].len<=lin.len) tail--; 84 seq[++tail]=lin; 85 } 86 inline LL query(int id){ 87 int r=a[id].r; 88 while(head<=tail && seq[head].r<r) head++; 89 if(head<=tail) return seq[head].len; 90 else return -1; 91 } 92 }que; 93 94 inline void solve(){ 95 n=getint(); 96 for(int i=1;i<=n;i++) a[i].l=getint(),a[i].r=getint(); 97 98 sort(a+1,a+n+1,cmp); 99 int last=0; 100 que.head=1; 101 for(int i=1;i<=n;i++) { 102 if(a[i].r>last) last=a[i].r,a[++cnt]=a[i],que.push(i);//得到去掉互相包含之后的新区间集,并加入单调队列,更新当前最右的端点值 103 else ans=max( que.query(i)*(a[i].r-a[i].l) , ans );//计算互相包含的情况 104 } 105 106 stack.init(); 107 for(int i=2;i<=cnt;i++) { 108 ans=max(ans,stack.query(i)),stack.push(i); 109 } 110 111 printf(OT,ans); 112 } 113 114 int main() 115 { 116 solve(); 117 return 0; 118 }
4、新斯诺克(snooker.cpp/c/pas)
【问题描述】
斯诺克又称英式台球,是一种流行的台球运动。在球桌上,台面四角以及两长边中心位置各有一个球洞,使用的球分别为 1 个白球,15 个红球和 6 个彩球(黄、绿、棕、蓝、粉红、黑)共22个球。击球顺序为一个红球、一个彩球直到红球全部落袋,然后以黄、绿、棕、蓝、粉红、黑的顺序逐个击球,最后以得分高者为胜。斯诺克的魅力还在于可以打防守球, 可以制造一些障碍球使对方无法击打目标球而被扣分。正是因为这样,斯诺克是一项充满神奇的运动。 现在考虑这样一种新斯诺克,设母球(母球即是白球,用于击打其他球)的标号为M,台面上有 N 个红球排成一排,每一个红球都有一个标号,`他们的标号代表了他们的分数。 现在用母球击打这些红球,一杆击打,如果母球接触到红球,就称为“K 到红球”。我们假设,一次可以击打任意多相邻连续的红球,也可以只击打一个球。并且红球既不会落袋,也不会相互发生碰撞,而只是停留在原处。每次击打时候,要想“K到红球”,至少要击打一个红球,如果想一次击打多个红球,那么击打的红球必须是依次连续排列的。 如果一次 “K 到红球”所有红球的标号之和的平均数大于母球的标号M,就获得了一个 “连击”。 现在请你计算总共能有多少种“连击”方案。
注意:如果当前有标号为 1、2、3的三种红球,母球标号为 0, 有如下 6 种获得“连击”方案: (1) 、 (2) 、 (3) 、 (1,2) 、 (2,3) 、 (1,2,3)
【输入文件】
输入文件共有两行,第一行是 N,M (N<=100000,M<=10000),
N 表示台面上一共有 N 个红球,M 表示母球的标号。
第二行是 N 个正整数,依次表示台面上 N 个红球的标号,所有标号均不超过10000。
【输出文件】
输出文件只有一个数,为“连击”的方案总数。
【输入样例】
4 3
3 7 2 4
【输出样例】
7
正解:归并排序,逆序对
解题报告:
刚看到这道题就想到POJ上刷的那道求序列的最大平均值的神题,那个是用斜率优化、凸包优化的DP,结果发现跟这道题并没有关系,被小胖犇狠狠嘲讽了一波。
静下心来推导了一波。发现一个神奇的性质:一个区间[i,j]满足条件当且仅当(前缀和)(sum[j]-sum[i-1])/(j-i) > m,移项:(sum[j]-sum[i-1])>(j-i)*m。。。高兴地发现这就是一个逆序对。。。
直接归并排序,求前缀和的逆序对。
1 //It is made by jump~ 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #include <algorithm> 8 #include <ctime> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #ifdef WIN32 14 #define OT "%I64d" 15 #else 16 #define OT "%lld" 17 #endif 18 using namespace std; 19 typedef long long LL; 20 const int MAXN = 100011; 21 int n,m; 22 int sum[MAXN],a[MAXN],jump[MAXN]; 23 int g[MAXN]; 24 LL ans; 25 26 inline int getint() 27 { 28 int w=0,q=0; 29 char c=getchar(); 30 while((c<'0' || c>'9') && c!='-') c=getchar(); 31 if (c=='-') q=1, c=getchar(); 32 while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); 33 return q ? -w : w; 34 } 35 36 inline void merge(int l,int mid,int r){ 37 int i=l,j=mid+1; 38 int cnt=l; 39 while(i<=mid && j<=r) { 40 if(jump[i]<=jump[j]) g[cnt++]=jump[i++]; 41 else{ 42 g[cnt++]=jump[j++]; 43 //ans+=mid-i+1; 44 ans+=(LL)mid; ans-=(LL)i; ans++; 45 } 46 } 47 while(i<=mid) g[cnt++]=jump[i++]; 48 while(j<=r) g[cnt++]=jump[j++]; 49 //for(;i<=mid;i++) g[cnt++]=a[i]; 50 //for(;j<=r;j++) g[cnt++]=a[i]; 51 for(i=l;i<=r;i++) jump[i]=g[i]; 52 } 53 54 inline void gui(int l,int r){ 55 if(l==r) return ; 56 int mid=(l+r)/2; 57 gui(l,mid); gui(mid+1,r); 58 merge(l,mid,r); 59 } 60 61 inline void solve(){ 62 n=getint();m=getint(); 63 for(int i=1;i<=n;i++) { 64 a[i]=getint(); sum[i]=sum[i-1]+a[i]; 65 jump[i]=sum[i]-m*i; 66 } 67 for(int i=1;i<=n;i++) jump[i]=-jump[i];//正序对变成逆序对 68 69 gui(0,n); 70 71 printf(OT,ans); 72 } 73 74 int main() 75 { 76 solve(); 77 return 0; 78 }