持續陰雨天氣有一定狀態加持。
屁嘞。三暴力說什麼呢
$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 }
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 }
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 }}
$=\sum\limits_{i=0}^{n} \sum\limits_{d=1}^{\frac{an+b}{c}} [\frac{ai+b}{c} \ge d ] $