Noip模拟55 2021.9.17(打表大胜利)
T1 skip
普通$dp$很好打:
$f[i]=max(f[j]-\sum_{k=1}^{K}k+a_i)$
就是要注意边界问题很烦人。
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 namespace AE86{ 5 inline int read(){ 6 int x=0,f=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 9 }inline void write(int x,char opt='\n'){ 10 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 11 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 12 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 13 }using namespace AE86; 14 15 const int NN=1e5+5,inf=0x3fffffff; 16 int n,a[NN],ans=-inf; 17 int dp[NN]; 18 inline int sigma(int st,int ed){ 19 if(st>ed) return 0; 20 return (st+ed)*(ed-st+1)/2; 21 } 22 23 namespace WSN{ 24 inline short main(){ 25 freopen("skip.in","r",stdin); 26 freopen("skip.out","w",stdout); 27 n=read();for(int i=1;i<=n;i++) a[i]=read(); 28 for(int i=1;i<=n;i++) dp[i]=-2000000000; 29 dp[0]=0; a[0]=-2000000000; 30 for(int i=1;i<=n;i++){ 31 for(int j=i-1;j>=0;j--){ 32 if(a[i]>=a[j]){ 33 dp[i]=max(dp[i],dp[j]-sigma(1,i-j-1)+a[i]); 34 } 35 } 36 } 37 int ans=-2000000000; 38 // for(int i=1;i<=n;i++) cout<<dp[i]<<" ";cout<<endl; 39 for(int i=1;i<=n;i++){ 40 ans=max(ans,dp[i]-sigma(1,n-i)); 41 } write(ans); 42 return 0; 43 } 44 } 45 signed main(){return WSN::main();}
然后正解很多,有用李超线段树的,还可以用数状数组,还可以$CDQ$分治,我用的$CDQ$分治,跟打怪差不多
只要是把方程式化成斜率式即可,打法可以参考打怪
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 namespace AE86{ 5 inline int read(){ 6 int x=0,f=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 9 }inline void write(int x,char opt='\n'){ 10 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 11 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 12 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 13 }using namespace AE86; 14 15 const int NN=3e5+5,inf=0x3fffffff; 16 int n,ans[NN]; 17 struct SNOW{ 18 int a,x,y,dp; 19 SNOW(){dp=-inf;} 20 }p[NN]; 21 inline bool cmp1(SNOW a,SNOW b){return a.a==b.a?a.x<b.x:a.a<b.a;} 22 inline bool cmp2(SNOW a,SNOW b){return a.x<b.x;} 23 inline double slope(int x,int y){return 1.0*(p[y].y-p[x].y)/(p[y].x-p[x].x);} 24 inline int sigma(int n){return (1+n)*n/2;} 25 int q[NN]; 26 inline void merge_sort(int l,int r){ 27 if(l>=r) return; int mid=(l+r)>>1; 28 merge_sort(l,mid); 29 sort(p+l,p+mid+1,cmp2); sort(p+mid+1,p+r+1,cmp2); 30 int h=1,t=0,j=l; 31 for(int i=mid+1;i<=r;i++){ 32 while(j<=mid && p[j].x<=p[i].x){ 33 while(h<t && slope(q[t],q[t-1])<=slope(q[t-1],j)) --t; 34 q[++t]=j; j++; 35 } 36 while(h<t && slope(q[h+1],q[h])>=-p[i].x) ++h; 37 if(h>t) continue; 38 ans[p[i].x]=p[i].dp=max(p[i].dp,p[q[h]].y+p[i].x*p[q[h]].x-sigma(p[i].x-1)+p[i].a); 39 p[i].y=p[i].dp-sigma(p[i].x); 40 } 41 sort(p+l,p+r+1,cmp1); 42 merge_sort(mid+1,r); 43 } 44 45 namespace WSN{ 46 inline short main(){ 47 freopen("skip.in","r",stdin); 48 freopen("skip.out","w",stdout); 49 n=read(); memset(ans,-0x3f,sizeof(ans)); 50 for(int i=1;i<=n;i++){ 51 p[i].a=read(); p[i].x=i; 52 ans[i]=p[i].dp=p[i].a-sigma(i-1); 53 p[i].y=p[i].dp-sigma(p[i].x); 54 } sort(p+1,p+n+1,cmp1); 55 merge_sort(1,n); 56 for(int i=1;i<n;i++) ans[n]=max(ans[n],ans[i]-sigma(n-i)); 57 write(ans[n]); 58 return 0; 59 } 60 } 61 signed main(){return WSN::main();}
T2 string
惊人的数据范围,考场上看到就跳了,但是只要找到优化方法就没那么变态了
发现在k特别大的时候可以把它消除掉,会是非常有规律的$abababa...abacdcdcdcd...cdcefefefef...efe$
然后可以缩小字符集,还可以缩小长度,这样会把长度缩小到$8$,然后状压记忆化
详细参考学长博客(因为我是学习的学长博客,而且是在9.19)
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 int k,n,s,sta,tim[30],a[30],r; 5 char ans[10]; 6 unordered_map<int,int> mp[10]; 7 inline int get(int x){return x?1<<4*x-4:0;} 8 inline int dfs(int len,int S,int rk,int last){ 9 if(mp[last].find(S)!=mp[last].end() && rk>=mp[last][S]) return mp[last][S]; 10 if(S==sta){ 11 if(!rk){puts(ans); exit(0);} 12 return mp[last][S]=1; 13 } 14 int tmp=0,r=0; 15 for(int ch=s;ch<26;ch++) if(ch+'a'!=ans[len-1]){ 16 ans[len]=ch+'a'; tim[ch]++; a[tim[ch]]++; 17 if(a[tim[ch]]<=k-tim[ch]+1){ 18 r=dfs(len+1,S+get(tim[ch])-get(tim[ch]-1),rk,tim[ch]); 19 tmp+=r; rk-=r; 20 } 21 --a[tim[ch]]; --tim[ch]; 22 } return mp[last][S]=tmp; 23 } 24 namespace WSN{ 25 inline short main(){ 26 freopen("string.in","r",stdin); 27 freopen("string.out","w",stdout); 28 scanf("%lld%lld",&k,&n); 29 while(k>8){ 30 for(int i=1;i<k;i++) putchar(s+'a'), putchar(s+'b'); 31 putchar(s+'a'); k-=2; s+=2; 32 } 33 for(int i=1;i<=k;i++) sta|=get(i); 34 dfs(0,0,n-1,9); puts("-1"); 35 return 0; 36 } 37 } 38 signed main(){return WSN::main();}
T3 permutation
可喜可贺,无法拿到首$A$但是拿到了第二滴血,毕竟首$A$是沈队
但是这题确实做了有够久,今天主要精力说这道题。。。
考场上一看是数学,就开始刚,刚到最后,不过有好的结果,就是$70$分
怎么来的? 打表!!!!
用打表来治数学题确实不错,但是这东西不能依赖,只是骗分的工具
但是这道题可以用打表干到满分
意外意外。。。。。。
先打$10$分暴力,列出组合后发现有一种神奇的层递关系,且与$k,m$的取值密切相关
大概是看他的每一列输出,都是类似$4,3,2,1,3,2,1,2,1,1$的东西,且和他前面那一列包含,前面那一列又是一个更长的循环
然后按照列是k,行是m打出一张表:
9 8 7 6 5 4 3 2 1 0 81 64 49 36 25 16 9 4 1 0 321 232 161 106 65 36 17 6 1 0 981 652 413 246 135 66 27 8 1 0 2565 1576 917 498 247 108 39 10 1 0 5997 3424 1841 918 415 164 53 12 1 0 12861 6856 3425 1578 655 236 69 14 1 0 25731 12862 5999 2568 985 326 87 16 1 0 48611 22872 10003 3998 1425 436 107 18 1 0 87507 38888 16009 6000 1997 568 129 20 1 0
(附加打表程序)
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 namespace AE86{ 5 inline int read(){ 6 int x=0,f=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 9 }inline void write(int x,char opt='\n'){ 10 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 11 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 12 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 13 }using namespace AE86; 14 15 const int NN=3e3+5,mod=1e9+7; 16 int n,m,k; 17 int biao[NN][NN]; 18 int precha[NN]; 19 int A[6005][4005],tmp; 20 vector<int> pai; 21 inline void dfs(int st,int cnt){ 22 if(cnt==k){ 23 ++tmp; 24 for(int i=0;i<pai.size();i++) A[tmp][i+1]=pai[i]; 25 return; 26 } 27 for(int i=st+1;i<=n;i++){ 28 pai.push_back(i); 29 dfs(i,cnt+1); 30 pai.pop_back(); 31 } 32 } 33 namespace WSN{ 34 inline short main(){ 35 freopen("perm.in","r",stdin); 36 freopen("perm.out","w",stdout); 37 n=read(); k=read(); m=read(); 38 if(n<=3){ 39 dfs(0,0); 40 int ans=0; 41 for(int i=1;i<tmp;i++) 42 (ans+=abs(A[i][m]-A[i+1][m]))%=mod; 43 write(ans); 44 return 0; 45 } 46 for(int i=1;i<=n;++i) 47 biao[i][n]=0, biao[i][n-1]=1, biao[i][n-2]=2*i, precha[i]=2; 48 for(int j=n-3;j;j--){ 49 biao[1][j]=n-j; int cha=biao[1][j]*(n-j-1)%mod; 50 for(int i=2;i<=n;i++){ 51 biao[i][j]=(biao[i-1][j]+cha)%mod; 52 cha=(cha+precha[i-1])%mod; precha[i-1]=cha; 53 } 54 } 55 write(biao[m][k]); 56 // for(int i=1;i<=n;i++){ 57 // for(int j=1;j<=n;j++){ 58 // printf("%5lld ",biao[i][j]); 59 // } cout<<endl; 60 // } 61 return 0; 62 } 63 } 64 signed main(){return WSN::main();}
不难发现他是一个高阶等差数列(按照每一列来看,公差数列分别是$0,0,0$,$2,2,2$,$6,8,10,12$,$12,20,..$)
每一列的公差的公差是上一列的公差,$O(n^2)$打表就有$70$
但是后来考后推发现这个高阶等差无法推出正解
于是我们用这张表找另一个规律(不对,换一张表):
__ 0__1__2__3__4__5 /n-k-1 0| 1 0 0 0 0 0 1| 0 1 2 3 4 5 2| 0 2 4 6 8 10 3| 0 3 9 17 27 39 4| 0 4 16 36 66 108 /m
行列关系如下,就是把刚才那张表倒一个个儿(行列没太对齐,是那个意思)
精心的找规律可以发现,他是有一个递推公式的设$f_{i,j}$表示$i$行$j$列的答案
发现:$f_{i,j}=f_{i-1,j}+f_{i,j-1}+j$,然后按照这个公式打,会有$70$分的$TLE$
考虑这个公式很像原来做的工业题
当时考场上也是干出了工业题的正解,因为行列打反痛失$A$题机会,印象深刻,所以就想到了
考虑公式意义,也是重要的题目转化:
你需要从$(i,j)$这个点走到$(n,m)$这个点,每次可以选择向下或者向右走一步,并花费你所在列编号个贡献。
求走到$(n,m)$这个点的总贡献。然后这道题的答案就变为了求$f_{m,n-k-1}$的答案。
可以直接使用组合数求出方案然后乘上贡献即可,注意在$m=1$这一行没有这个规律,但是他们可以直接算出组合数不乘贡献(或理解为贡献为1)
这样可以打出可以优化到正解的$70$分$TLE$做法:
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 namespace AE86{ 5 inline int read(){ 6 int x=0,f=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 9 }inline void write(int x,char opt='\n'){ 10 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 11 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 12 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 13 }using namespace AE86; 14 15 const int NN=1e6+5,mod=1e9+7; 16 int n,m,k,ans; 17 int h[NN],v[NN]; 18 inline int qmo(int a,int b){ 19 int ans=1,c=mod; a%=c; 20 while(b){ 21 if(b&1) ans=ans*a%c; 22 b>>=1; a=a*a%c; 23 } return ans; 24 } 25 inline void pre(){ 26 h[0]=h[1]=1; v[0]=v[1]=1; 27 for(int i=2;i<NN;i++) h[i]=h[i-1]*i%mod; 28 v[NN-1]=qmo(h[NN-1],mod-2); 29 for(int i=NN-2;i>=2;i--) v[i]=v[i+1]*(i+1)%mod; 30 } 31 inline int C(int n,int m){ 32 if(n<m||n<0||m<0) return 0; 33 return h[n]*v[n-m]%mod*v[m]%mod; 34 } 35 namespace WSN{ 36 inline short main(){ 37 freopen("perm.in","r",stdin); 38 freopen("perm.out","w",stdout); 39 n=read(); k=read(); m=read(); pre(); 40 n-=k+1; 41 for(int i=1;i<=m;i++){ 42 for(int j=0;j<=n;j++){ 43 ans=(ans+(i==1?1:j)*C(n-j+m-i,m-i)%mod)%mod; 44 } 45 } 46 write(ans); 47 return 0; 48 } 49 } 50 signed main(){return WSN::main();}
然后对于同一列的组合数贡献一样,那么可以使用前缀和优化,因为组合数在杨辉三角同一列的前缀和为其末尾右下角的值
$\sum_{i=m}^{n}C_{i}^{m}=C_{n+1}^{m+1}$,这样每一列的不包括$m=1$的直接处理,然后$m=1$的再处理一遍,总共$O(n)$扫两边
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 namespace AE86{ 5 inline int read(){ 6 int x=0,f=1;char ch=getchar(); 7 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 8 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 9 }inline void write(int x,char opt='\n'){ 10 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 11 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 12 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 13 }using namespace AE86; 14 15 const int NN=1e6+5,mod=1e9+7; 16 int n,m,k,ans; 17 int h[NN],v[NN]; 18 inline int qmo(int a,int b){ 19 int ans=1,c=mod; a%=c; 20 while(b){ 21 if(b&1) ans=ans*a%c; 22 b>>=1; a=a*a%c; 23 } return ans; 24 } 25 inline void pre(){ 26 h[0]=h[1]=1; v[0]=v[1]=1; 27 for(int i=2;i<NN;i++) h[i]=h[i-1]*i%mod; 28 v[NN-1]=qmo(h[NN-1],mod-2); 29 for(int i=NN-2;i>=2;i--) v[i]=v[i+1]*(i+1)%mod; 30 } 31 inline int C(int n,int m){ 32 if(n<m||n<0||m<0) return 0; 33 return h[n]*v[n-m]%mod*v[m]%mod; 34 } 35 namespace WSN{ 36 inline short main(){ 37 freopen("perm.in","r",stdin); 38 freopen("perm.out","w",stdout); 39 n=read(); k=read(); m=read(); 40 if(n==k) return puts("0"),0; pre(); n-=k+1; 41 if(m>=2) for(int i=0;i<=n;i++) ans=(ans+(n-i)*C(m+i-1,m-2)%mod)%mod; 42 for(int i=0;i<=n;i++) ans=(ans+C(m+i-1,m-1))%mod;//小于2的特判,不符合规律 43 write(ans); 44 return 0; 45 } 46 } 47 signed main(){return WSN::main();}
然后正解可以去看:
ZXS
T4 小P的生成树
把题解上面的$tan$换一下(因为他反了),剩下的找他的做就可以了,思路只要是见过这类题型就可以记住
1 #include<bits/stdc++.h> 2 #define pii pair<double,double> 3 #define mp make_pair 4 #define fi first 5 #define se second 6 #define int long long 7 using namespace std; 8 namespace AE86{ 9 inline int read(){ 10 int x=0,f=1;char ch=getchar(); 11 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f; 13 }inline void write(int x,char opt='\n'){ 14 char ch[20];int len=0;if(x<0)x=~x+1,putchar('-'); 15 do{ch[len++]=x%10+(1<<5)+(1<<4);x/=10;}while(x); 16 for(register int i=len-1;i>=0;--i)putchar(ch[i]);putchar(opt);} 17 }using namespace AE86; 18 19 const int NN=55,MM=205; 20 int n,m,fa[NN],cnt; 21 double si[80000],ans; 22 struct node{int u,v,x,y;double val;}p[MM]; 23 inline double Tan(int yi,int er){ 24 return 1.0*(p[er].x-p[yi].x)/(p[er].y-p[yi].y); 25 } 26 inline pii Sita(int yi,int er){ 27 return mp(1.0*atan(Tan(yi,er)),1.0*atan(Tan(yi,er))+3.1415926); 28 } 29 inline int getfa(int x){return fa[x]=((fa[x]==x)?x:getfa(fa[x]));} 30 inline bool cmp(node a,node b){ 31 return a.val>b.val; 32 } 33 inline void krus(){ 34 int seg=0; sort(p+1,p+m+1,cmp); 35 double A=0.0,B=0.0; 36 for(int i=1;i<=n;i++) fa[i]=i; 37 for(int i=1;i<=m;i++){ 38 int u=p[i].u,v=p[i].v; 39 if(getfa(u)!=getfa(v)){ 40 fa[getfa(u)]=getfa(v); 41 ++seg; A+=p[i].x; B+=p[i].y; 42 } 43 if(seg==n-1) break; 44 } ans=max(ans,(double)sqrt(1.0*A*A+1.0*B*B)); 45 } 46 namespace WSN{ 47 inline short main(){ 48 freopen("mst.in","r",stdin); 49 freopen("mst.out","w",stdout); 50 n=read(); m=read(); 51 for(int i=1;i<=m;i++){ 52 int u=read(),v=read(),a=read(),b=read(); 53 p[i]=(node){u,v,a,b,0.0}; 54 } 55 for(int i=1;i<m;i++) for(int j=i+1;j<=m;j++){ 56 pii now=Sita(i,j); si[++cnt]=now.fi; si[++cnt]=now.se; 57 } si[++cnt]=-1.5707964; si[++cnt]=4.7123891; 58 sort(si+1,si+cnt+1); 59 for(int i=2;i<=cnt;i++){ 60 double jia=(si[i]+si[i-1])/2.0; 61 for(int j=1;j<=m;j++){ 62 p[j].val=(double)(1.0*p[j].x*cos(jia)+1.0*p[j].y*sin(jia)); 63 } krus(); 64 } 65 double jia=(si[cnt]+si[1])/2.0; 66 for(int j=1;j<=m;j++){ 67 p[j].val=(double)(1.0*p[j].x*cos(jia)+1.0*p[j].y*sin(jia)); 68 } krus(); 69 printf("%.6lf\n",ans); 70 return 0; 71 } 72 } 73 signed main(){return WSN::main();}