Codeforces1257D (贪心+缓存,或者二分+线段树)
题目大意:有n个怪物,每个怪物都有一个能力值a。有m个勇士,每个勇士都有一个力量P和耐力S。明天可以选择一个勇士去打怪物,必须按顺序打,如果这个勇士的能力P>=a那么就可以打败这个怪物,就必须打下一个怪物,而且最多一天只能打S个怪物。如果力量P<a。这个勇士就回来。这天结束,每个勇士可以使用无数次。问最少需要多少天,能把所有的怪物全部打败。
给的数据范围分别是 n(1<=n<=2e5), m(1<=m<=2e5) (1<=pi<=1e9,1<=si<=n) 所有询问的n+m加起来不超过2e5
这道题一个很正常的思路(或者说我的一般思路是) 首先我们怎么来求解决了前i只怪物的最小时间?????
那么就可以考虑解决第i个怪物的勇士他所解决的区间的问题,这样的操作可以二分解决,然后在用线段树维护?????
但是感觉这样有点难写?而且很容易wa,我就wa了不知道多少遍。
下面是比赛时写的代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cmath> 6 #include <bitset> 7 #include <set> 8 typedef long long ll; 9 using namespace std; 10 const int maxn=201000; 11 int t,n,m; 12 int num[maxn],sum1[maxn<<2],sum2[maxn<<2],dp[maxn],q[maxn],p[maxn],s[maxn]; 13 struct node{ 14 int pi,si; 15 }; 16 node fnum[maxn]; 17 18 bool cmp(node n1,node n2){ 19 if(n1.pi==n2.pi) return n1.si<n2.si; 20 else return n1.pi<n2.pi; 21 } 22 23 void build1(int l,int r,int rt){ 24 if(l==r){ 25 sum1[rt]=num[l]; 26 return; 27 } 28 int mid=(l+r)/2; 29 build1(l,mid,rt<<1); 30 build1(mid+1,r,rt<<1|1); 31 sum1[rt]=max(sum1[rt<<1],sum1[rt<<1|1]); 32 } 33 34 int ask(int l,int r,int rt,int L,int R){ 35 if(L<=l&&r<=R) return sum1[rt]; 36 int mid=(l+r)/2; 37 int ans=0; 38 if(L<=mid) ans=max(ans,ask(l,mid,rt<<1,L,R)); 39 if(R>mid) ans=max(ans,ask(mid+1,r,rt<<1|1,L,R)); 40 return ans; 41 } 42 43 void adds(int l,int r,int rt,int k,int k1){ 44 if(l==r){ 45 sum2[rt]=k1; 46 return; 47 } 48 int mid=(l+r)/2; 49 if(k<=mid) adds(l,mid,rt<<1,k,k1); 50 else adds(mid+1,r,rt<<1|1,k,k1); 51 sum2[rt]=min(sum2[rt<<1|1],sum2[rt<<1]); 52 } 53 54 int Min(int l,int r,int rt,int L,int R){ 55 if(L<=l&&r<=R) return sum2[rt]; 56 int mid=(l+r)/2; 57 int ans=(int)(1e9+7); 58 if(L<=mid) ans=min(ans,Min(l,mid,rt<<1,L,R)); 59 if(R>mid) ans=min(ans,Min(mid+1,r,rt<<1|1,L,R)); 60 return ans; 61 } 62 63 64 bool check(int l,int r){ 65 int q1=ask(1,n,1,l,r); 66 int iscan=(int)(lower_bound(p+1,p+m+1,q1)-p); 67 //if(l==1&&r==2) printf("%d %d %d %d\n",q1,num[1],num[2],iscan); 68 if(iscan>=1&&iscan<=m&&q[iscan]>=(r-l+1)) return true; 69 return false; 70 } 71 72 int main(){ 73 scanf("%d",&t); 74 while(t--){ 75 scanf("%d",&n); 76 for(int i=1;i<=n;i++) scanf("%d",&num[i]); 77 build1(1,n,1); 78 scanf("%d",&m); 79 for(int i=1;i<=m;i++) scanf("%d%d",&fnum[i].pi,&fnum[i].si); 80 sort(fnum+1,fnum+m+1,cmp); 81 int flag=0; 82 for(int i=1;i<=m;i++){ 83 //if(fnum[i].pi<num[i]) flag=1; 84 p[i]=fnum[i].pi;s[i]=fnum[i].si; 85 } 86 for(int i=1;i<=n;i++) if(fnum[m].pi<num[i]) flag=1; 87 if(flag==1){printf("-1\n");continue;} 88 89 q[m]=fnum[m].si; 90 for(int i=m-1;i>=1;i--) q[i]=max(q[i+1],s[i]); 91 92 dp[1]=1;dp[0]=0; 93 adds(0,n,1,1,1); adds(0,n,1,0,0); 94 for(int i=2;i<=n;i++){ 95 int l=1,r=i,ans=i; 96 while(l<=r){ 97 int mid=(l+r)/2; 98 if(check(mid,i)==1){ans=mid;r=mid-1;} 99 else l=mid+1; 100 } 101 // cout<<ans-1<<" "<<i-1<<endl; 102 dp[i]=Min(0,n,1,ans-1,i-1)+1; 103 adds(0,n,1,i,dp[i]); 104 } 105 printf("%d\n",dp[n]); 106 } 107 return 0; 108 }
然后就是一个很妙的解法了,我也不知道怎么才能在考试里面想到这样的解法??????
tql!!!
(1):首先我们可以发现这个东西是可以贪心的,就是对于每个勇士他可以杀的怪兽越多越好,总而言之就是能啥多只怪兽就啥多少怪兽,直接贪心下去就ok了
(2):然后我们需要抛弃m这个东东,因为没啥用,考虑能连续作战i天的勇士的fi的最大值,这样的话我们如果能坚持i天,就直接撸下去,而不需要考虑那个特定的勇士
(3):直接将值域映射到i上面,这样是最好做的
下面是代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <iostream> 5 #include <cmath> 6 #include <bitset> 7 typedef long long ll; 8 using namespace std; 9 const int maxn=201000; 10 int num[maxn],mx[maxn]; 11 int t,n,m; 12 int main(){ 13 scanf("%d",&t); 14 while(t--){ 15 scanf("%d",&n); 16 for(int i=1;i<=n;i++) scanf("%d",&num[i]); 17 for(int i=1;i<=n;i++) mx[i]=0; 18 scanf("%d",&m); 19 for(int i=1;i<=m;i++){ 20 int pi,si; 21 scanf("%d%d",&pi,&si); 22 mx[si]=max(mx[si],pi); 23 } 24 for(int i=n-1;i>=1;i--) mx[i]=max(mx[i],mx[i+1]); 25 26 int pos=1,flag=1,day=0; 27 while(pos<=n){ 28 day++; int tmp=pos,maxn=0; 29 while(tmp<=n){ 30 maxn=max(maxn,num[tmp]); 31 if(maxn>mx[tmp-pos+1]) break; 32 ++tmp; 33 } 34 if(tmp==pos){ 35 flag=0;break; 36 } 37 pos=tmp; 38 } 39 if(!flag) day=-1; 40 printf("%d\n",day); 41 } 42 return 0; 43 }