这大概是进信息组以来考得最萎的一次了,宛如一夜回到解放前。。。
没有一道题完整地打对了,下面对做题情况做一个检讨
T1:理解错题意了,QAQ;
T2:逆推的时候没有想到很好的计算方法,但测1e18发现我的计算方法只会少了一点,就天真的while加了一下,于是T飞了;
T3:一开始打了一个n^3的DP,然后打了一个n^2贪心对拍,结果把n^3暴力交上去了
T4:想成区间型DP了,但是要保证不下降所以转移和状态记录很困难,最后5分钟草草地打了一个暴搜,结果连暴力分都没拿全
T5:是原题,莫名其妙的打了一个离散化,结果离散化的数组开小了
T6:当时觉得是一个巨码农的细节题,就放弃去做T4了
这一次考试:第一有的题是真的不会做,第二是是会做的分并没有全部拿到,写萎的现象很严重
这一次暴露了对一些问题解决上的缺陷:DP,贪心以及有代码难度的暴搜题,对于简单题一遍打对能力较弱
以后要训练基础题的切题能力,刷洛谷之类的OJ,要保证较简单的题目的分拿稳。基础和代码能力尚弱也是目前做省选难度题速度较慢的主要原因
下面发一下订正的题的题解:
T2:主要是讲一下怎么逆推回去
原来是每20个里面去掉一个,所以原来的每一个20现在就只剩下了19
if(dis[x]%19==0) 那么dis[y]=dis[x]*(20/19);<恰好有(dis[x]/19)份20,无零头>
if(dis[x]%19) dis[y]=dis[x]/19+1+dis[x];<有(dis[x]/19)份20,还有一份是不足20的零头扣的也要加上>
// MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> #include<queue> #define RG register using namespace std; typedef long long ll; const int N=500050; const ll Inf=1e18+10; int gi(){ int x=0,flag=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') flag=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*flag; } int mark[N],n,m,k,head[N],to[N],nxt[N],cnt,vis[N],S,T; long long dis[N]; void lnk(RG int x,RG int y){ to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt; } struct data{ int x;long long d; bool operator <(const data b) const{return d>b.d;} }; priority_queue <data> q; int main(){ freopen("transport.in","r",stdin); freopen("transport.out","w",stdout); n=gi(),m=gi(),k=gi(); for(RG int i=1;i<=n;i++){ int x=gi(),y=gi();mark[x]=y; } for(RG int i=1;i<=m;i++){ int x=gi(),y=gi(); lnk(x,y);lnk(y,x); } S=gi(),T=gi(); for(RG int i=1;i<=n;i++) dis[i]=Inf; dis[T]=k;q.push((data){T,k}); while(!q.empty()){ int x=q.top().x;q.pop(); if(vis[x])continue;vis[x]=1; for(RG int i=head[x];i;i=nxt[i]){ int y=to[i]; if(mark[x]){ if(dis[y]>dis[x]+1) dis[y]=dis[x]+1,q.push((data){y,dis[y]}); } else{ int ans; if(dis[x]%19==0) ans=dis[x]/19*20; else ans=dis[x]/19+1+dis[x]; if(dis[y]>ans) dis[y]=ans,q.push((data){y,dis[y]}); } } } printf("%lld ",dis[S]); return 0; }
T3:
考虑按左端点排序后每次O(n)的贪心调整策略,用一个单调队列并不断调整;
如果两个区间发生矛盾则优先考虑右端点小的并弹掉右端点大的,因为这样对后面的影响最小且不会影响前面的。。。
首先我们可以去掉一些永远不可能构成答案的区间<即完整覆盖了别的区间的区间,选被他覆盖的区间显然不差于选他自己>
通过这一步操作之后<可以用单调队列实现>我们可以发现变成了一堆左端点小的右端点也一定小的区间;
类似这样:
|______|
|______|
|_______|
|________|
那么我们从一个区间出发的最优策略就是每次走到左端点大于该区间右端点的第一个区间,然后一直执行此操作;
然后我们考虑询问区间,那么肯定是找第一个左端点大于等于l的区间开始(因为这个区间同时意味着右端点最小)
在保证不跳出区间的范围内尽量的移动,这种带条件的序列上的快速移动一般用倍增优化,然后就可以AC
// MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> #define RG register using namespace std; typedef long long ll; const int N=200050; int gi(){ int x=0,flag=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') flag=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*flag; } struct data{ int l,r; }q[N],p[N]; inline bool check(RG int l,RG int r,RG int L,RG int R){ if(L<=l&&l<=R) return 1; if(L<=r&&r<=R) return 1; if(l<=L&&L<=r) return 1; if(l<=R&&R<=r) return 1; return 0; } inline bool cmp(const data &a,const data &b){ if(a.l==b.l) return a.r<b.r; else return a.l<b.l; } int n,Q,tail,L[N],f[20][N],step[20][N]; inline void pre(){ tail=0;p[0].l=-1000,p[0].r=-500; for(RG int i=1;i<=n+1;i++){ if(!check(p[tail].l,p[tail].r,q[i].l,q[i].r)) p[++tail]=q[i]; else if(q[i].r<p[tail].r){ while(q[i].r<p[tail].r) tail--; p[++tail]=q[i]; } else if(q[i].r>=p[tail].r&&q[i].l!=p[tail].l) p[++tail]=q[i]; } } int main(){ freopen("query.in","r",stdin); freopen("query.out","w",stdout); n=gi(),Q=gi(); for(RG int i=1;i<=n;i++){ q[i].l=gi(),q[i].r=gi(); } q[n+1].l=1e9+1,q[n+1].r=1e9+2; sort(q+1,q+1+n+1,cmp);pre(); for(RG int i=1;i<=tail;i++) L[i]=p[i].l; for(RG int i=1;i<tail;i++){ f[0][i]=upper_bound(L+1,L+tail+1,p[i].r)-L; step[0][i]=1; } q[0].l=1e9+1,q[0].r=1e9+2; for(RG int j=1;j<=16;j++){ for(RG int i=1;i<=tail;i++){ f[j][i]=f[j-1][f[j-1][i]]; step[j][i]=step[j-1][i]+step[j-1][f[j-1][i]]; } } for(RG int i=1;i<=Q;i++){ RG int l=gi(),r=gi(); RG int x=upper_bound(L+1,L+tail+1,l-1)-L; RG int ans=0; if(l<=p[x].l&&p[x].r<=r){ ans=1; for(RG int j=16;j>=0;j--){ if(f[j][x]&&l<=p[f[j][x]].l&&p[f[j][x]].r<=r){ ans+=step[j][x];x=f[j][x]; } } } printf("%d ",ans); } return 0; }
T4:
两种方法:
1.f[i][j]表示从1到i这个位置花费j次合并为一个不降序列的最后一个元素的最小值<最后一个元素最小对后面的转移更优>
然后加一个前缀和优化;
转移:f[i][k+i-j-1]=min{sum[i]-sum[j]}(sum[i]-sum[j]>=f[j][k]时转移,即把j+1到i合并后加到以j结尾的不降序列上,合并了i-(j+1)次)
2.f[i][j]表示从1到i这个位置不降序列长度为j的最后一个元素的最小值<同理>
转移:f[i][k+1]=min{sum[i]-sum[j]}(sum[i]-sum[j]>=f[j][k],即把j+1到i合并后加入到以j结尾的长度为k的不降序列上,不降序列的长度加了1)
对于这种方法,f[i][j]表示的合并次数为i-j;
这两种方法通过巧妙的设计把不降序列的最后一位记录了下来,方便转移
// MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=100050; const int Inf=19260817; int gi() { int x=0,flag=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') flag=-1;ch=getchar();} while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*flag; } int a[N],f[350][350],sum[N],n; int main(){ freopen("seq.in","r",stdin); freopen("seq.out","w",stdout); n=gi();for(int i=1;i<=n;i++) a[i]=gi(),sum[i]=sum[i-1]+a[i]; for(int i=1;i<=n;i++) for(int j=0;j<=n;j++) f[i][j]=Inf; f[0][0]=0; for(int i=1;i<=n;i++) for(int j=0;j<i;j++) for(int k=0;k<=j;k++){ int h=sum[i]-sum[j]; if(h>=f[j][k]){ f[i][k+i-j-1]=min(f[i][k+i-j-1],h); } } for(int i=0;i<=n;i++) if(f[n][i]!=Inf){ printf("%d ",i);return 0; } return 0; }