【做題】Ocd、Mancity、Captcha
Ocd
令人震驚的一點在於,像我一樣無腦puts("-1")的,居然有40分。
然而經大佬指點,在每一位上求填某個數對答案的貢獻是可以直接計算出來的。
具體實現是通過dp預處理。定義dp[i][j]為1~i的排列中逆序對數為j的方案數。
對於狀態轉移,枚舉新的一位填上了第幾大的數。
於是我們有 dp[i][j]=∑dp[i-1][k] (j-i+1<=k<=j)。對此可以用前綴和進行優化。
因此預處理的時間復雜度為O(n*k)。
然後,我們一位位地計算答案。設當前為第i位,目前選取的數是j,需要有k組逆序對,j在可選數的字典序中排第val[j]個,那麽選取j的貢獻就是dp[n-i][k-val[j]+1]。
因此,我們可以當前位按字典序從小到大進行枚舉,如果其貢獻值大於等於p則必然選取這個數,否則把p減去其貢獻值。
具體實現時用used數組記錄每個數是否可選,並通過差分優化val數組的計算。
時間復雜度O(n*k+n^2)。
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 const int N=1010,K=10010,INF=1e12; 5 int dp[N][K],g[N][N],val[N],ans[N],num[N]; 6 bool used[N]; 7 int n,k,p;8 int get_dp(int x,int y) 9 { 10 if(y==0) return dp[x][y]; 11 return dp[x][y]-dp[x][y-1]; 12 } 13 signed main() 14 { 15 scanf("%lld%lld%lld",&n,&k,&p); 16 for(int i=1;i<=n;i++) 17 for(int j=1;j<=n;j++) scanf("%lld",&g[i][j]); 18 for(int i=0;i<=k;i++) dp[1][i]=1,dp[0][i]=i+1; 19 for(int i=2;i<=n;i++) 20 { 21 for(int j=0;j<=k;j++) 22 { 23 if(j-i<0) dp[i][j]+=dp[i-1][j]; 24 else dp[i][j]+=dp[i-1][j]-dp[i-1][j-i]; 25 dp[i][j]=min(dp[i][j],INF); 26 dp[i][j]+=dp[i][j-1]; 27 } 28 } 29 if(get_dp(n,k)<p) return 0*puts("-1"); 30 int le=k,now=p; 31 for(int i=1;i<=n;i++) 32 { 33 memset(val,0,sizeof(val)); 34 for(int j=1;j<=n;j++) if(used[j]) val[j+1]--; 35 for(int j=1;j<=n;j++) val[j]+=val[j-1]; 36 for(int j=1;j<=n;j++) val[j]+=j-1; 37 for(int j=1;j<=n;j++) num[j]=val[g[i][j]]; 38 for(int j=1;j<=n;j++) if(!used[g[i][j]]&&num[j]<=le) 39 { 40 if(get_dp(n-i,le-num[j])>=now) 41 { 42 le-=num[j]; 43 ans[i]=g[i][j]; 44 used[g[i][j]]=1; 45 break; 46 } 47 else now-=get_dp(n-i,le-num[j]); 48 } 49 } 50 for(int i=1;i<=n;i++) printf("%lld ",ans[i]); 51 return 0*printf("\n"); 52 }
Mancity
非常令人在意的是每條邊邊權只為1或2的性質。我由此得出了每個小時(除了最後一個)必然走d或d-1的長度。於是我由此確定答案的上下界,random了一下,騙了60分。
然而那還需要卡常的正解並沒有用到這個性質。
註意到路徑是可以拓展的,也就是說,記top[i][j]為i結點像根節點最遠能到達的點,那麽top[i][j]=top[top[i][k]][j-k] (0<=k<=j)。
以及如下這個結論:對於路徑的兩個端點a,b,以及它們的LCA z,我們可以讓a和b盡可能地向z移動,最後如果a,b重合則得到答案,如果a,b的距離小於等於d則讓ans+1,否則讓ans+2,然後這就是最優解。
對於這個結論的正確性,假設有更優解,那麽記它跨越z的那個小時是從c到d(c是初始a的祖先),那麽要麽最後a(b)一定與c(d)重合或在其上面,就是其中一個符合這個條件,另一個可以通過不多於1次的移動來滿足這一條件。而先前的每一次移動都是貪心最優的,因此這一定是最優解。
於是我們可以充分地利用倍增的思想,對上述算法進行優化。
具體就是記錄top[i][j]表示從i向上移動2^j步所最遠能到達的點。(求top[i][0]可以用倍增或二分,求LCA我又用了一個倍增)。
時間復雜度O(n*logn)。(然而我還要用快讀優化)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=500010,MAXPOS=19; 4 int n,d,q; 5 int fa[N][MAXPOS+1],dis[N][MAXPOS+1],top[N][MAXPOS+1],deep[N]; 6 void prework() 7 { 8 int le,pos; 9 for(int i=1;i<=n;i++) 10 { 11 for(int j=1;j<=MAXPOS;j++) 12 { 13 fa[i][j]=fa[fa[i][j-1]][j-1]; 14 dis[i][j]=dis[i][j-1]+dis[fa[i][j-1]][j-1]; 15 } 16 le=d;pos=i; 17 for(int j=MAXPOS;j>=0;j--) 18 { 19 if(dis[pos][j]<=le) le-=dis[pos][j],pos=fa[pos][j]; 20 } 21 top[i][0]=pos; 22 for(int j=1;j<=MAXPOS;j++) 23 { 24 top[i][j]=top[top[i][j-1]][j-1]; 25 } 26 } 27 } 28 int lca(int a,int b) 29 { 30 if(deep[a]<deep[b]) swap(a,b); 31 for(int i=MAXPOS;i>=0;i--) 32 { 33 if(deep[fa[a][i]]>=deep[b]) a=fa[a][i]; 34 } 35 if(a==b) return a; 36 for(int i=MAXPOS;i>=0;i--) 37 { 38 if(fa[a][i]!=fa[b][i]) 39 a=fa[a][i],b=fa[b][i]; 40 } 41 return fa[a][0]; 42 } 43 int main() 44 { 45 int x,y,ans,z; 46 scanf("%d%d%d",&n,&d,&q); 47 deep[1]=1; 48 for(int i=2;i<=n;i++) 49 { 50 scanf("%d%d",&x,&y); 51 fa[i][0]=x; 52 dis[i][0]=y; 53 deep[i]=deep[x]+1; 54 } 55 prework(); 56 for(int i=1;i<=q;i++) 57 { 58 ans=0; 59 scanf("%d%d",&x,&y); 60 z=lca(x,y); 61 for(int j=MAXPOS;j>=0;j--) 62 { 63 if(deep[top[x][j]]>deep[z]) x=top[x][j],ans+=(1<<j); 64 if(deep[top[y][j]]>deep[z]) y=top[y][j],ans+=(1<<j); 65 } 66 if(x!=y) 67 { 68 if(dis[x][MAXPOS]+dis[y][MAXPOS]-2*dis[z][MAXPOS]<=d) ans++; 69 else ans+=2; 70 } 71 printf("%d\n",ans); 72 } 73 return 0; 74 }
Captcha
又是喜聞樂見的水題了,然而帶有碼農屬性。
(用gets讀入導致我爆30,而另一個用gets的同學爆0了。)
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=100010; 4 char pic[8][N*7]; 5 int num,sum,n,ans[N]; 6 int main() 7 { 8 scanf("%d",&n); 9 getchar(); 10 for(int i=1;i<=6;i++) 11 { 12 for(int j=1;j<=n;j++) 13 scanf("%s",pic[i]+7*j-6); 14 } 15 int pos=0,x,y; 16 for(int i=1;i<=n;i++,pos+=7) 17 { 18 num=0; 19 for(int j=1;j<=6;j++) 20 { 21 for(int k=1;k<=6;k++) 22 { 23 if(pic[j][k+pos]==‘.‘) 24 { 25 if(num==0) y=k+pos; 26 num++; 27 } 28 } 29 if(num>0) 30 { 31 x=j; 32 break; 33 } 34 } 35 if(pic[x][y+1]==‘.‘&&pic[x][y+2]==‘.‘) 36 { 37 if(pic[x+1][y]==‘.‘&&pic[x+1][y+2]==‘.‘) 38 { 39 if(pic[x+2][y+1]==‘#‘) ans[i]=0; 40 else if(pic[x+3][y]==‘#‘) ans[i]=9; 41 else ans[i]=8; 42 continue; 43 } 44 if(pic[x+4][y]==‘#‘) 45 { 46 ans[i]=7; 47 continue; 48 } 49 if(pic[x+3][y]==‘.‘) 50 { 51 if(pic[x+3][y+1]==‘#‘&&pic[x+3][y+2]==‘.‘) ans[i]=6; 52 else ans[i]=2; 53 continue; 54 } 55 if(pic[x+1][y]==‘#‘) ans[i]=3; 56 else ans[i]=5; 57 } 58 else 59 { 60 if(pic[x+1][y-1]==‘.‘) ans[i]=4; 61 else ans[i]=1; 62 continue; 63 } 64 } 65 for(int i=1;i<=n;i++) 66 printf("%d ",ans[i]); 67 return 0; 68 }
小結:靠rp騙了70分,然後又莫名被坑了70分。
【做題】Ocd、Mancity、Captcha