1. 程式人生 > 其它 >Noip模擬55 2021.9.17(打表大勝利)

Noip模擬55 2021.9.17(打表大勝利)

T1 skip T2 string T3 permutation T4 小P的生成樹

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();}
View Code

然後正解很多,有用李超線段樹的,還可以用數狀陣列,還可以$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$做法: