暑期集训第九天(6-30)题解及总结
小概括:
今天的考试中老师可能对我们过于高估了...四道之中出了两道紫题,于是这次考试之中的分数基本都很低(除了AK的gyz大佬),DZN今天终于超过了lc(排除提示的问题)今天晚上周围集体都在打树剖,可能认为老师明天要考???
T1:浇水
听说这道题要用贪心?线段树?对不起,最短路解决一切问题,我们考虑题目中要求覆盖整个花园,其实可以转化为覆盖上边界(下边界同理),我们就可以处理出每个喷子的可以喷到的上边界的范围,之后就是把他们拼起来即可,之后就和昨天的最后一道题一样了,注意喷子够不到上边界的要判掉,不然就算成负权的加入图中了
(链接跳转:昨日博客:https://www.cnblogs.com/li-jia-hao/p/13210357.html
老姚贪心写法https://www.cnblogs.com/hbhszxyb/p/13212743.html;)
#include<bits/stdc++.h> using namespace std; const int N=1e6+10; struct Node{ int next,to,dis; }edge[N]; int Head[N],tot; void Add(int x,int y,int z){ edge[++tot].to=y; edge[tot].dis=z; edge[tot].next=Head[x]; Head[x]=tot; } struct Edge{ int id,dis; Edge(int x,int y){ id=x;dis=y; } bool operator < (const Edge &a)const{ return a.dis<dis; } }; priority_queue<Edge>q; int dis[N],vis[N]; void dijkstra(int x){ memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[x]=0;q.push(Edge(x,0)); while(!q.empty()){ int u=q.top().id;q.pop(); if(vis[u]) continue; vis[u]=1; for(int i=Head[u];i;i=edge[i].next){ int v=edge[i].to; if(dis[v]>dis[u]+edge[i].dis){ dis[v]=dis[u]+edge[i].dis; q.push(Edge(v,dis[v])); } } } } int main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int k,n,m; scanf("%d%d%d", &k, &n, &m); for(int i = 1; i <= k; ++ i){ int x,y,l,r; scanf("%d%d",&x,&y); x++; if(y < m/2) continue; l=x-(int)sqrt(y*y-m*m/4); r=x+(int)sqrt(y*y-m*m/4); //printf("%d %d %d\n",i,l,r); l=max(l,1); r=min(r,n+1); Add(l,r+1,1); } for(int i=1;i<=n+2;++i) Add(i+1,i,0); dijkstra(1); if(dis[n]==0x3f3f3f3f) printf("-1\n"); else printf("%d\n",dis[n+1]); return 0; }
T2:免费馅饼
这道题后来被削弱了,老师看我们对字典序不怎么理解,就把第二部分的路径输入给去了(然鹅我自信满满的dp只得了30pts),如果不考虑路径的话这道题还是挺简单的,就是一个线性dp,我们预先处理出i时在j可以得到的分数,dp[i][j]表示时间为i时在j的答案,于是我们从答案可能转移来的方向进行转移求最大值即可,我30pts是因为忘了原地踏步的情况,记得初始化及特判高度为1的情况,
#include<bits/stdc++.h> using namespace std; const int N=1e6+10; int score[1500][150]; int dp[1500][150]; //i秒的时候在j时的答案; int n,m,t_max,sum; int main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); scanf("%d%d",&n,&m); int a,b,c,d; while(scanf("%d%d%d%d",&a,&b,&c,&d)==4){ if(m==1){ score[a][b]+=d;sum+=d; t_max=max(a,t_max); continue; } if((m-1)%c!=0) continue; int t=((m-1)/c); score[a+t][b]+=d; t_max=max(a+t,t_max); } memset(dp,128,sizeof(dp)); dp[0][(n+1)/2]=0; for(int i=1;i<=t_max;++i) for(int j=1;j<=n;++j){ if(dp[i][j]<dp[i-1][j]+score[i][j]){ dp[i][j]=dp[i-1][j]+score[i][j]; } if(j>1) if(dp[i][j]<dp[i-1][j-1]+score[i][j]){ dp[i][j]=dp[i-1][j-1]+score[i][j]; } if(j<=n-2) if(dp[i][j]<dp[i-1][j+2]+score[i][j]){ dp[i][j]=dp[i-1][j+2]+score[i][j]; } if(j<=n-1) if(dp[i][j]<dp[i-1][j+1]+score[i][j]){ dp[i][j]=dp[i-1][j+1]+score[i][j]; } if(j>2) if(dp[i][j]<dp[i-1][j-2]+score[i][j]){ dp[i][j]=dp[i-1][j-2]+score[i][j]; } } int ans=0; for(int i=1;i<=n;++i){ ans=max(ans,dp[t_max][i]); } if(n==1&&m==1){ printf("%d\n",sum); return 0; } printf("%d\n",ans); return 0; }
T3:压缩
这是考试之中我唯一一道A掉的dp题,我们用dp[i][j][k]表示从i到j的情况,k=1表示有M,否则表示没有M,那么我们存在以下几个转移:
1. dp[i][j][0]=min(dp[i][j][0],dp[i][mid][0]+1)(当且仅当字符串对称)
2. dp[i][j][0]=min(dp[i][j][0],dp[i][k][0]+j-k)
3. dp[i][j][1]=min(dp[i][j][1],min(dp[i][k][1],dp[i][k][0])+min(dp[k+1][j][1],dp[k+1][j][0])+1)(最后加的一是缺失的M);
之后我们闭着眼进行转移就行了.
#include<bits/stdc++.h> #define debug printf("-debug-\n") using namespace std; const int N=1e6+10; char a[N]; int dp[250][250][2]; bool Judge(int l,int r){ if((r-l+1)%2!=0) return 0; int mid=l+(r-l+1)/2; while(mid<=r){ if(a[l]!=a[mid]) return 0; l++;mid++; } //debug; return 1; } int main(){ //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); scanf(" %s",a+1); int len=strlen(a+1); for(int i=1;i<=len;++i) for(int j=i;j<=len;++j) dp[i][j][0]=dp[i][j][1]=j-i+1; //debug; for(int d=2;d<=len;++d) for(int i=1,j;(j=i+d-1)<=len;++i){ //debug; int mid=i+(j-i+1)/2-1; if(Judge(i,j)) dp[i][j][0]=min(dp[i][j][0],dp[i][mid][0]+1); for(int k=i;k<=j;++k) dp[i][j][0]=min(dp[i][j][0],dp[i][k][0]+j-k); for(int k=i;k<j;++k) dp[i][j][1]=min(dp[i][j][1],min(dp[i][k][1],dp[i][k][0])+min(dp[k+1][j][1],dp[k+1][j][0])+1); } //debug; printf("%d\n",min(dp[1][len][1],dp[1][len][0])); return 0; }
T4:枪战Maf
我们老师介绍这道题属于那种看起来就有思路,越看越有思路,但是越写越绝望的题目,因为考场上没时间细想,不怎么以为然,知道我开始详细的打这道题....
这道题一看就能想到tarjan缩点,但是如果这样的话你就掉进出题人的陷阱了,这道题的思路我感觉...自己想出来的非神即犇.
考虑如果单独的一个环,最大的情况就是一个人打完后立马被另一个人打死,这样死的人数就是环的大小减一,最大就是环的大小除以2
如果是链那?最多人数当然是除了出度为0的点的人以外全部死掉,最少人数就是大小处以二.我们可以对每个人进行处理,入度为0的点必不死,可死可不死的人可以打上标记,稍后处理,之后对环单独处理,这里的思路可能有些不好想,推一下老师的博客:https://www.cnblogs.com/hbhszxyb/p/13212782.html
#include<bits/stdc++.h> using namespace std; const int N=1e6+10; int die[N],undie[N]; int q[N],Max,Min,ru[N],to[N]; int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d",&to[i]); ru[to[i]]++; } for(int i=1;i<=n;++i) if(ru[i]==0){ Min++; q[++Max]=i; } int head=0; while(head<=Max){ int top=q[head];head++; if(die[to[top]]) continue; die[to[top]]=1; int live=to[to[top]]; ru[live]--; undie[live]=1; if(ru[live]==0) q[++Max]=live; } for(int i=1;i<=n;++i) if(ru[i]&&!die[i]){ int len=0,flag=0; for(int j=i;!die[j];j=to[j]){ len++; flag|=undie[j]; die[j]=1; } if(!flag && len>1) Min++; Max+=len/2; } printf("%d %d",n-Max,n-Min); return 0; }
总结:
今天的考试我感觉还是挺难的,虽然出现了集训以来的第一个AK(G大佬),但是对于我们这些小萌新来说就是虐杀呀......平时只练蓝题还是不够,平时还是要穿插一些有难度的题目呀,高三学长们马上要高考了,在现在的机房也待不了几天了,希望明天可以取得一个好成绩吧,.