Noip模拟35 2021.8.10
考试题目变成四道了,貌似确实根本改不完。。。
不过给了两个小时颓废时间确实很爽(芜湖~~)
但是前几天三道题改着不是很费劲的时候为什么不给放松时间,
非要在改不完题的时候颓??
算了算了不碎碎念了。。
T1 玩游戏
好多大神在考场上使用乱搞做法$A$掉了这道题,但是我只水了$20$就跑去刚$T2$了
但是大神们的做法会被其他的恶心数据卡掉,样例是随的所以飞快。。
正解是比较$diao$的双指针。记录五个变量:
$sum,sum1,sum2,max1,max2$分别表示$l-r$的和,$k-l$的和,$k-r$的和,$k-l$之间的最大值,$k-r$之间的最大值。
从$k$开始分别先找到使$sum1,sum2$小于$0$的点,记录下$pos1,pos2,max1,max2$
然后开始跳指针,判断条件是整个的$l-r$是否可以跨过记录的两个坎——$max1,max2$
能跨过就加上,再进行跳指针,不断的跳。。最后能到两端就返回就行。
当然会出现左右都跨不过坎的情况,直接$break$掉,不用害怕,还没跳完。
记录最后两个指针跳到的位置,看看从两边向中间跳能不能跳到刚才过不去的位置。
类似上述的操作再来一边就行。复杂度$O(n)$,非常优秀了。
代码还有一些细节,类似读入的时候不用管$a[1]$,因为根本用不到。
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 inline int read(){ 5 int x=0,f=1; char ch=getchar(); 6 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 7 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 8 return x*f; 9 } 10 const int inf=1e18; 11 int T,n,k,a[100005],l,r; 12 namespace WSN{ 13 inline bool wsn(int l,int r){ 14 int sum=0,sum1=0,sum2=0; 15 int max1=-inf,max2=-inf; 16 int pos1,pos2; 17 for(int i=l-1;i>=0;i--){ 18 sum1+=a[i]; max1=max(max1,sum1); 19 if(sum1<=0) {pos1=i;break;} 20 } 21 for(int i=r+1;i<=n+1;i++){ 22 sum2+=a[i]; max2=max(max2,sum2); 23 if(sum2<=0) {pos2=i;break;} 24 } 25 while(1){ 26 if(l==1&&r==n) return 1; 27 if(sum+max1<=0&&pos1){ 28 sum+=sum1; sum1=0; l=pos1; max1=-inf; 29 for(int i=l-1;i>=0;i--){ 30 sum1+=a[i]; max1=max(max1,sum1); 31 if(sum1<=0) {pos1=i;break;} 32 } 33 } 34 else if(sum+max2<=0&&pos2<=n){ 35 sum+=sum2; sum2=0; r=pos2; max2=-inf; 36 for(int i=r+1;i<=n+1;i++){ 37 sum2+=a[i]; max2=max(max2,sum2); 38 if(sum2<=0) {pos2=i;break;} 39 } 40 } 41 else break; 42 } 43 if(l>r) return 0; 44 sum=0; for(int i=1;i<=n;i++) sum+=a[i],a[i]=-a[i]; 45 if(sum>0) return 0; 46 sum1=sum2=0; max1=max2=a[l]=a[r]=-inf; 47 int ll=0,rr=n+1; 48 for(int i=ll+1;i<=l;i++){ 49 sum1+=a[i]; max1=max(max1,sum1); 50 if(sum1<=0) {pos1=i;break;} 51 } 52 for(int i=rr-1;i>=r;i--){ 53 sum2+=a[i]; max2=max(max2,sum2); 54 if(sum2<=0) {pos2=i;break;} 55 } 56 while(1){ 57 if(ll+1==l&&rr-1==r) return 1; 58 if(sum+max1<=0&&pos1!=l){ 59 sum+=sum1; sum1=0,ll=pos1; max1=-inf; 60 for(int i=ll+1;i<=l;i++){ 61 sum1+=a[i]; max1=max(max1,sum1); 62 if(sum1<=0) {pos1=i;break;} 63 } 64 } 65 else if(sum+max2<=0&&pos2!=r){ 66 sum+=sum2; sum2=0,rr=pos2; max2=-inf; 67 for(int i=rr-1;i>=r;i--){ 68 sum2+=a[i]; max2=max(max2,sum2); 69 if(sum2<=0) {pos2=i;break;} 70 } 71 } 72 else return 0; 73 } 74 } 75 inline short main(){ 76 T=read(); 77 while(T--){ 78 n=read();k=read();memset(a,0,sizeof(a)); 79 for(int i=0;i<n;i++) a[i]=read(); 80 --n,--k; a[0]=a[n+1]=-inf; l=k+1,r=k; 81 puts(wsn(l,r)?"Yes":"No"); 82 } 83 return 0; 84 } 85 } 86 signed main(){return WSN::main();}
T2 排列
还没改出来,快了,先沽了。。。。
不对,$50$分很好打,先打暴力全排,在找$k==1$的规律,直接$2^{n-1}$即可。
或许这就是我死刚$T2$的原因。接下来说一下考场思路:
$k==1$时,我以排列里最大值的位置作为列,最大值的值作为行,打出了一个杨辉三角。
//行是长度为n的排列里面的最大值,也就是n //列是n的位置 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 。 。 。
不甘心一个"数学"题只拿$50$分的我决定打出$k==2$的同样原理的表,令我惊喜的是,他与杨辉三角有巨大关系
0 0 0 1 0 1 5 3 3 5 21 20 18 20 21 93 105 110 110 105 93 459 558 645 700 645 558 459 。 。 。 。 //不难发现每一项都是杨辉三角对应项的整倍数
于是我想要找到倍数能否按照组合数的原理推出关系。。。
于是
$\textit{2 hours later.......}$
$woc$,不行了,后面题还没动呢。。。跑了
就只有五十分了。。。
希望以后看到这篇博客的$OIer$不管是谁,想出这种东西的留下一个见解,我一定会看的
$UPD:2021.8.18$ 突然发现改出来了没填坑,来补坑了
$dp$数组表示很神,$sm_{i,j,0/1,0/1}$表示序列长度为$i$,操作了$j$次,序列里最大的那个值在左侧还是右侧的序列数
然后关于$dp$,方程式十分的形象,将一个序列以最大值为断点分开(枚举断点),那么分开的两个序列的$0/1$分别按照最大值的位置表示就行
然后剩下的数为了保证是一个排列,用组合数求方案$\binom{i-1}{k-1}$即可,处理出的是一个类似前缀和的东西,减一下就是答案
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 inline int read(){ 5 int x=0,f=1; char ch=getchar(); 6 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 7 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 8 return x*f; 9 } 10 const int NN=10005; 11 int n,m,p,C[NN][NN],sm[NN][22][2][2]; 12 namespace WSN{ 13 inline short main(){ 14 n=read(); m=read(); p=read(); 15 for(register int i=0;i<=n;++i){ 16 C[i][0]=1; 17 for(register int j=1;j<=i;++j) 18 C[i][j]=(C[i-1][j-1]+C[i-1][j])%p; 19 } 20 for(register int i=0;i<=m+1;++i) sm[0][i][0][0]=sm[0][i][0][1]=sm[0][i][1][0]=sm[0][i][1][1]=1; 21 for(register int i=1;i<=n;++i) for(register int j=1;j<=m+1;++j) for(register int k=1;k<=i;++k){ 22 (sm[i][j][0][0]+=sm[k-1][j][0][1]*sm[i-k][j][1][0]%p *C[i-1][k-1])%=p; 23 (sm[i][j][0][1]+=sm[k-1][j][0][1]*sm[i-k][j-1][1][1]%p*C[i-1][k-1])%=p; 24 (sm[i][j][1][0]+=sm[k-1][j-1][1][1]*sm[i-k][j][1][0]%p*C[i-1][k-1])%=p; 25 (sm[i][j][1][1]+=(sm[k-1][j][1][1]*sm[i-k][j][1][1]-(sm[k-1][j][1][1]-sm[k-1][j-1][1][1])*(sm[i-k][j][1][1]-sm[i-k][j-1][1][1]))%p*C[i-1][k-1])%=p; 26 } 27 printf("%lld\n",((sm[n][m][0][0]-sm[n][m-1][0][0])%p+p)%p); 28 return 0; 29 } 30 } 31 signed main(){return WSN::main();}
T3 最短路
比较神仙的$dp$。
考虑从$n$到$1$的路径,一定是先往回走一段,再沿着$1$到$n$的路径走一段,再往回走一段,再沿着$1$到$n$的路径走一段
$zxs$:这样省钱。。。
确实!
我们设$f_{i,j}$表示向下走的边的,从$i-j$最小花费。$g_{i,j}$表示向上走的边的,从$i-j$最小花费。
看着图大概理解一下,代码比较清楚,建边用$spfa$转移,注意代码里面将二维的$dp$变成了一维的,加个$n*n$判断是正向反向边
1 #include<bits/stdc++.h> 2 using namespace std; 3 inline int read(){ 4 int x=0,f=1; char ch=getchar(); 5 while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 6 while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar();} 7 return x*f; 8 } 9 const int NN=62505; 10 int n,m,w[255],dis[255][255],d[NN<<1]; 11 bool g[255][255],vis[NN<<1]; 12 vector< pair<int,int> > e[NN<<1]; 13 int id(int x,int y){return (x-1)*n+y;} 14 inline void die_for_100(){ 15 memset(dis,0x3f,sizeof(dis)); 16 for(int i=1;i<=n;i++) dis[i][i]=0; 17 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(g[i][j]) dis[i][j]=w[j]; 18 for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) 19 dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); 20 } 21 inline void hand_made_link(int x,int y,int z){e[x].push_back(make_pair(y,z));} 22 inline void spfa(){ 23 queue<int> Q; 24 memset(d,0x3f,sizeof(d)); 25 d[id(n,n)]=0; vis[id(n,n)]=1; 26 Q.push(id(n,n)); 27 while(!Q.empty()){ 28 int x=Q.front();Q.pop(); vis[x]=0; 29 for(int i=0;i<e[x].size();i++){ 30 int y=e[x][i].first,z=e[x][i].second; 31 if(d[y]>d[x]+z){ 32 d[y]=d[x]+z; 33 if(!vis[y]){ 34 Q.push(y); 35 vis[y]=1; 36 } 37 } 38 } 39 } 40 } 41 namespace WSN{ 42 inline short main(){ 43 n=read();m=read(); for(int i=1;i<=n;i++) w[i]=read(); 44 for(int i=1;i<=m;i++) g[read()][read()]=1; 45 die_for_100(); 46 for(int i=1;i<=n;i++) 47 for(int j=1;j<=n;j++) 48 for(int k=1;k<=n;k++) 49 if(dis[j][k]<1e9&&j!=k) hand_made_link(id(i,j),id(k,i)+n*n,dis[j][k]-w[k]); 50 for(int i=1;i<=n;i++) 51 for(int j=1;j<=n;j++) 52 for(int k=1;k<=n;k++) 53 if(dis[i][k]<1e9&&dis[k][j]<1e9) hand_made_link(id(i,j)+n*n,id(i,k),dis[i][k]+dis[k][j]); 54 spfa(); 55 int ans=d[id(1,1)]+w[1]; 56 if(ans<1e9) printf("%d\n",ans); 57 else printf("-1\n"); 58 return 0; 59 } 60 } 61 signed main(){return WSN::main();}
T4 矩形
没改出来,菇沽姑