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();}View Code8 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 shortmain(){ 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();}View Code
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();}View Code
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();}RE 70(打表)
不難發現他是一個高階等差數列(按照每一列來看,公差數列分別是$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)$這個點的總貢獻。
可以直接使用組合數求出方案然後乘上貢獻即可,注意在$m=1$這一行沒有這個規律,但是他們可以直接算出組合數不乘貢獻(或理解為貢獻為1)
這樣可以打出可以優化到正解的$70$分$TLE$做法: