1. 程式人生 > >【做題】Ocd、Mancity、Captcha

【做題】Ocd、Mancity、Captcha

二分 個數 style sin etc 屬性 一點 name 這一

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