[考試反思]0508省選模擬91:小雨

持續陰雨天氣有一定狀態加持。

屁嘞。三暴力說什麼呢

$T1$的$70$分暴力如果不想複雜了就很好做(頭腦簡單是好事)

正解是個四維偏序,想倒不難想,但是寫個錘子啊(然而牛神的確寫出來了。。)

$T2$的話剛開始想 偏了絲毫頭緒沒有。

後來發現用一些小套路的話$O(n^2)$其實沒有那麼難想(二進制逐位考慮和字典序逐位考慮哪個不套路了)

然後其實正解已經想到了但是證明很模糊。剩餘時間不多於是就去保$50$了

$T3$寫的其實是$50$的算法。但是不可避免的炸精了。不可避免。那就算了。

總體還是不錯的。

 

T1:最長公共子序列

大意:求$4$個串的$LCS$。保證前三個串中,每個串裏相同元素至多出現$2$次。$n \le 10^4$

對於部分分:四個串都是排列。

做法比較簡單:求出排列的逆數組$ia,ib,ic,id$。我們從$1$到$n$枚舉$d$序列中的元素。

那麼$j$可以轉移到$i$的條件就是$j<i,ia[d[j]]<ia[d[i]],ib[d[j]]<ib[d[i]],ic[d[j]]<ic[d[i]]$

這個可以直接做$O(n^2)$。

然後當不再是排列的時候,因爲前三個串每個數可能有$2$次,那麼$ia,ib,ic$可能有兩個元素。

所以對於一個$d[i]$可以拆成$2^3$個四元組,然後做四維偏序問題。

要麼$dcq$套$cdq$。要麼$cdq$最內存樹狀數組換爲二維的。

注意到這裏的轉移要求的是嚴格小於而不是小於等於,所以對於每次分治區間找中點的時候。

需要把中點移動到兩側不相等的位置。總複雜度是$O(nlog^3n)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 10001
 4 int dp[S<<3],n,sc;short t[S][S];
 5 vector<int>ia[S],ib[S],ic[S];
 6 struct st{int a,b,c,d,o;}s[S<<3],R[S<<3];
 7 bool cmp(st x,st y){return x.a==y.a?x.o<y.o:x.a<y.a;}
 8 void add(int x,int r,short w){for(;x<=n;x+=x&-x)for(int y=r;y<=n;y+=y&-y)t[x][y]=max(t[x][y],w);}
 9 int ask(int x,int r,short a=0){for(;x;x^=x&-x)for(int y=r;y;y^=y&-y)a=max(t[x][y],a);return a;}
10 void clear(int x,int r){for(;x<=n;x+=x&-x)for(int y=r;y<=n;y+=y&-y)t[x][y]=0;}
11 void CDQ(int l,int r){
12     if(s[l].d==s[r].d)return;
13     int md=l+r>>1;
14     if(s[l].d!=s[md].d&&l<md)while(s[md].d==s[md-1].d)--md;
15     else{while(s[md].d==s[md+1].d)++md;++md;}
16     CDQ(l,md-1);
17     for(int i=l;i<=r;++i)R[i]=s[i];
18     sort(R+l,R+md,cmp); sort(R+md,R+r+1,cmp);
19     int p1=l,p2=md;
20     while(p2<=r)
21         if(p1<md&&R[p1].a<R[p2].a)add(R[p1].b,R[p1].c,dp[R[p1].o]),p1++;
22         else dp[R[p2].o]=max(dp[R[p2].o],ask(R[p2].b-1,R[p2].c-1)+1),p2++;
23     for(int i=l;i<p1;++i)clear(R[i].b,R[i].c);
24     CDQ(md,r);
25 }
26 int main(){
27     scanf("%d",&n);
28     for(int i=1,a;i<=n;++i)scanf("%d",&a),ia[a].push_back(i);
29     for(int i=1,b;i<=n;++i)scanf("%d",&b),ib[b].push_back(i);
30     for(int i=1,c;i<=n;++i)scanf("%d",&c),ic[c].push_back(i);
31     for(int i=1,d;i<=n;++i){
32         scanf("%d",&d);
33         for(int a=0;a<ia[d].size();++a)for(int b=0;b<ib[d].size();++b)for(int c=0;c<ic[d].size();++c)
34             dp[++sc]=1,s[sc]=(st){ia[d][a],ib[d][b],ic[d][c],i,sc};
35     }
36     CDQ(1,sc);
37     for(int i=1;i<=sc;++i)dp[0]=max(dp[0],dp[i]);
38     printf("%d\n",dp[0]);
39 }
View Code

 

T2:排列

大意:給定$n$個元素$x_i$,構造數組$p$,最小化$\max\limits_{i=1}^{n-1} (x_{p_i} \ xor \  x_{p_{i+1}})$。$n \le 3 c\times 10^5$

首先,因爲是鄰位異或,所以我們逐位考慮。

如果所有數字在某一位上都是$1$,那麼把它們同時變成$0$沒有影響。

這樣之後,取出最高位,一定有一些數字是$1$有一些數字是$0$。

不妨稱這一位上是$1$的數爲大數。反之成爲小數。

我們先不考慮字典序,我們只嘗試找到最小的ans$值。

那麼可以發現一種解,滿足所有大數相鄰,所有小數相鄰,然後中間只有一個大數和一個小數相鄰。

大數之間的異或值在最高位上一定是$0$,小數也是。只有大數和小數相鄰的部分是$1$

所以我們只在意大小相鄰的情況。問題轉變爲:選出一個大數和一個小數,最小化異或和。直接$01trie$解決。

現在我們得到了$ans$值,然後相鄰兩個數 要麼屬於同一組,要麼異或和恰好爲$ans$

現在再來考慮字典序。那肯定就是逐位試填,看剩餘數是否合法。

具體檢驗的話,當前填下的數爲$x$,假設它屬於小組,那麼接下來還有合法填法當且僅當滿足三者之一:

  • 大組爲空
  • 小組爲空,且存在$x \ xor \ ans$(稱之爲$x$的配對元素)
  • 去掉元素$x$後,依然存在配對關係

開桶維護每種值剩餘個數。全局變量記錄剩餘配對數。可以做到$O(n^2)$或$O(n^2log\ n)$

可以發現在上面這三條的約束下你下一位的最優決策只有這幾種:

  • 走到另一組中的配對元素
  • 走到本組下標最小的數
  • 走到本組下標次小的數

如果要走到對面組那只有第一種選擇。

否則如果要走到本組,你肯定想走到下標最小的。

爲什麼還要考慮次小的呢?因爲有可能剩下的元素中只有一個配對關係,就是下標最小的那個。

所以爲了以後還能換組,你現在必須先把當前組走完,最後再去走下標最小的才能合法。

所以說這樣複雜度就是$O(nlogn)$之類的了。多拿一些$vector/set$維護每個值出現下標的最小值就行。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 struct hash_map{
 4     #define _ 10000007
 5     int fir[_],l[_],to[_],v[_],ec;
 6     int&operator[](int x){int r=x%_;
 7         for(int i=fir[r];i;i=l[i])if(to[i]==x)return v[i];
 8         l[++ec]=fir[r];fir[r]=ec;to[ec]=x;return v[ec];
 9     }
10 }M,R;
11 #define S 300005
12 int a[S],n,tc[2][S<<5],rt,pc,w=1<<30,al[S],A=(1<<30)-1,mx,c[2],lst,ord,v[5],vc;long long mt;
13 void insert(int&p,int x,int al){
14     if(!p)p=++pc;if(al==-1)return;
15     insert(tc[x>>al&1][p],x,al-1);
16 }
17 int ask(int p,int x,int al){
18     if(al==-1)return 0;
19     return tc[x>>al&1][p]?ask(tc[x>>al&1][p],x,al-1):ask(tc[x>>al&1^1][p],x,al-1)|1<<al;
20 }
21 set<int>s[2],ss[S];
22 int b(int x){return x&mx?1:0;}
23 void del(int x){printf("%d ",x);ss[R[a[x]]].erase(x);s[b(a[x])].erase(x);lst=a[x];}
24 bool chk(int x){return mt||(!c[b(x)^1]||(!c[b(x)]&&M[x^w]));}
25 int main(){
26     scanf("%d",&n);
27     for(int i=1;i<=n;++i)scanf("%d",&a[i]),A&=a[i];
28     for(int i=1;i<=n;++i)a[i]^=A,mx=max(mx,a[i]),M[a[i]]++,R[a[i]]=++ord;
29     while(mx!=(mx&-mx))mx^=mx&-mx;
30     for(int i=1;i<=n;++i)if(a[i]&mx)insert(rt,a[i],30);
31     for(int i=1;i<=n;++i)if(!(a[i]&mx))w=min(w,ask(rt,a[i],30));
32     for(int i=1;i<=n;++i)if(a[i]&mx)mt+=M[a[i]^w],s[1].insert(i),c[1]++;
33         else c[0]++,s[0].insert(i);
34     for(int i=1;i<=n;++i)ss[R[a[i]]].insert(i);
35     for(int i=1;i<=n;++i){
36         int x=a[i]; M[x]--;c[b(x)]--;mt-=M[x^w];
37         if(chk(x)){del(i);break;}
38         M[x]++;c[b(x)]++;mt+=M[x^w];
39     }
40     for(int i=2;i<=n;++i){
41         vc=0;
42         if(s[b(lst)].size())v[++vc]=*s[b(lst)].begin();
43         if(s[b(lst)].size()>1)v[++vc]=*(++s[b(lst)].begin());
44         if(M[w^lst])v[++vc]=*ss[R[w^lst]].begin();
45         sort(v+1,v+1+vc);
46         for(int j=1;j<=vc;++j){
47             int x=a[v[j]]; if((x^lst)>w)continue;
48             M[x]--;c[b(x)]--;mt-=M[x^w];
49             if(chk(x)){del(v[j]);break;}
50             M[x]++;c[b(x)]++;mt+=M[x^w];
51         }
52     }
53 }
View Code

 

T3:加農炮

大意:求$n \times m$點陣中,傾斜角第$k$小的點的座標(並列者按座標大小排序)。多測。$n,m \le 10^6,T \le 10^4$

首先一個暴力的思路是,我們可以二分這個傾斜角,就能找到第$k$個點在哪條直線上。

如果能知道這條直線的解析式(也就是斜率,但要求分數形式不能是小數),那麼就可以輕鬆回答點的座標。

所以我們有兩個問題,一個是怎麼求二分一個斜率後,這條直線上方有多少個點,另一個是要解決分數問題。

並不會針對分數的二分,這時候偉大的數據結構$Stern-Brocot\ Tree$出現了。

原理大概是這樣的,最開始我們有一個分數集合$\{ \frac{0}{1},\frac{1}{0} \}$。

然後每一輪,我們對於集合中相鄰的兩個元素$\frac{a}{b},\frac{c}{d}$,我們把$\frac{a+c}{b+d}$插入在它們之間。

可以證明,這樣會形成所有的分數。當然這個樹也是無限的。

實現的話,假象你有一棵樹,然後調用函數$build(a,b,c,d)$分別表示$\frac{a}{b},\frac{c}{d}$

那麼就會形成一個新的分數$\frac{a+c}{b+d}$。然後再遞歸的調用$build(a,b,a+c,b+d),build(a+c,b+d,c,d)$

然後我們要解決二分分數的問題,於是只要在這棵樹上二分就可以啦。

但是仔細一想卻不太對勁,樹上二分是$O(log)$的?平時見過的是線段樹二分。

之所以複雜度是$O(log)$的,是因爲線段樹的深度是$O(log)$的,你一定會遞歸到葉節點的。

在$SB$樹上就不能這麼猖狂了。因爲例如$\frac{1}{1000000}$這個分數的深度就是$1000000$。你要是這麼二分會T$掉。

所以我們可以採取更神奇的方法:每次去二分沿着當前方向走幾步。

這樣最大二分次數也就是左一下右一下了,每次轉彎至少翻倍,所以一共二分$log$次,總共需要的$check$次數是$O(log^2)$的。

現在我們差的是一個低複雜度的$check$

暴力的話,我們可以枚舉每個橫座標,然後判斷這個座標上有多少個點在線之下。假如斜率是$\frac{p}{q}$

那麼點的個數就是$\sum\limits_{i=0}^{n-1} \lfloor \frac{ip}{q} \rfloor$

一個經典的類歐幾里得形式,我們來推推式子吧,首先把問題轉化爲一般形式:

(爲了方便,下面所有除法默認向下取整)

求$F(a,b,c,n)=\sum\limits_{i=0}^{n} \frac{ai+b}{c}$

首先比較簡單的是$\frac{ai+b}{c} = \frac{a}{c} i + \frac{b}{c} + \frac{(a \mod c \times i + b \mod c}{c}$

這樣前兩項就可以提出來了,我們得到$F(a,b,c,n)=F(a \mod c,b \mod c,c,n) + \frac{n(n+1)}{2} \frac{a}{c}  + n \frac{b}{c}$

這樣接下來我們就只需要考慮$a,b <c$

$F(a,b,c)=\sum\limits_{i=0}^{n} \frac{ai+b}{c}$

$=\sum\limits_{i=0}^{n} \sum\limits_{d=1}^{\frac{an+b}{c}} [\frac{ai+b}{c} \ge d ] $

$=\sum\limits_{d=0}^{\frac{an+b}{c}-1} \sum\limits_{i=0}^{n} [\frac{ai+b}{c} \ge d+1 ] $

$=\sum\limits_{d=0}^{\frac{an+b}{c}-1} \sum\limits_{i=0}^{n} [ai \ge dc+c-b ] $

$=\sum\limits_{d=0}^{\frac{an+b}{c}-1} \sum\limits_{i=0}^{n} [ai > dc+c-b+1 ] $

$=\sum\limits_{d=0}^{\frac{an+b}{c}-1} \sum\limits_{i=0}^{n} [i > \frac{dc+c-b+1}{a} ] $

$=\sum\limits_{d=0}^{\frac{an+b}{c}-1} (n+1 - \sum\limits_{i=0}^{n} [i \le \frac{dc+c-b+1}{a} ] )$

$= (n+1) \frac{an+b}{c} - F(c,c-b-1,a,\frac{an+b}{c} +1 )$

遞歸求解。複雜度與$gcd$類似。故本題總複雜度是$O(Tlog^3n)$

 1 #include<cstdio>
 2 #define ll long long
 3 ll min(ll x,ll y){return x<y?x:y;}
 4 ll _Euler(ll a,ll b,ll c,ll n){
 5     if(!a)return b/c*(n+1);
 6     if(b>=c||a>=c)return a/c*n*(n+1)/2+b/c*(n+1)+_Euler(a%c,b%c,c,n);
 7     return (a*n+b)/c*n-_Euler(c,c-b-1,a,(a*n+b)/c-1);
 8 }
 9 ll cal(ll n,ll m,ll fz,ll fm){
10     if(!fz)return 0; if(!fm)return n*m-n;
11     if(fz*(m-1)<=fm*(n-1))return _Euler(fz,fz,fm,m-2)+m-1-min((m-1)/fm,(n-1)/fz);
12     return n*m-cal(m,n,fm,fz)-1-min((m-1)/fm,(n-1)/fz);
13 }
14 int main(){int t;scanf("%d",&t);while(t--){
15     ll n,m,k;scanf("%lld%lld%lld",&n,&m,&k); k--;
16     if(k>=n*m-n){printf("%lld 1\n",k-n*m+n+2);continue;}
17     ll fz1=0,fm1=1,fz2=1,fm2=0,l,r,md,inf=1e9;
18     while(1){
19         l=-1;r=min(fm2?(m-1-fm1)/fm2:inf,fz2?(n-1-fz1)/fz2:inf)+1;
20         if(r==1)break;
21         while(md=l+r>>1,r-l>1)if(cal(n,m,fz1+md*fz2,fm1+md*fm2)<=k)l=md;else r=md;
22         fz1+=l*fz2;fm1+=l*fm2;
23         l=-1;r=min(fm1?(m-1-fm2)/fm1:inf,fz1?(n-1-fz2)/fz1:inf)+1;
24         if(r==1)break;
25         while(md=l+r>>1,r-l>1)if(cal(n,m,fz2+md*fz1,fm2+md*fm1)>k)l=md;else r=md;
26         fz2+=l*fz1;fm2+=l*fm1;
27     }k-=cal(n,m,fz1,fm1)-1;printf("%lld %lld\n",k*fz1+1,k*fm1+1);
28 }}
View Code

 

 

 

 

 

$=\sum\limits_{i=0}^{n} \sum\limits_{d=1}^{\frac{an+b}{c}} [\frac{ai+b}{c} \ge d ] $

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章