前言
- 比賽連結。
排名歷程:\(3→5→3\),因為 \(T1\) 的 special judge
是後來加上的,導致部分人掛了分,賽後安排了重測,就變成了 \(rank5\),賽後發現 \(T1\) 資料過水,重新更新了資料,卡掉了很多人的假做法,又成了 \(rank3\)。
T1
已知合法的分組有 \(\begin{cases} 0~0~0\\ 1~1~1\\ 2~2~2\\ 0~1~2\\ \end{cases}\)
-
唐氏做法:
統計 \(cnt_0,cnt_1,cnt_2\) ,找規律發現除了其中兩個 \(\bmod 3=2\) ,剩下一個 \(\bmod 3<2\) 時,將 \(<2\) 的一組中拆出來兩個補給其餘兩個形成 \(0~1~2\) ,這樣 \(ans-1+2\),更優。
其餘情況均為將其各自分組,最後餘下的形成 \(0~1~2\) 即可。
由此 \(if~else\) 大模擬即可。
-
正解:
考慮列舉 \(0~1~2\) 的組數即可。
T2
- 部分分 \(30pts\): \(n\le 1e7\) 的情況,暴力即可。
T3
-
部分分 \(30pts\):爆搜即可。
-
部分分 \(40pts\):再處理一下 \(k=1\) 的情況即可。
-
正解 \(100pts\):
-
結論:
忽略字典序,將所有
&
放在前面,所有|
放在後面一定是最優情況。-
證明:
有 \(\begin{cases} x|y\ge \max(x,y)\\ x\&y\le \min(x,y)\\ x\&y\le x|y\\ \end{cases}\)
因此有 \(x\&y|z\ge \max(x\&y,z)\ge \min(x|y,z)\ge x|y\&z\)。
解釋一下,若 \(x\&y\ge z\) ,\(\because x|y\ge x\&y\),\(∴ \max(x\&y,z)=x\&y\ge z=\min(x|y,z)\);
反之若 \(x\&y<z\),\(\because x|y\ge x\&y\),\(∴ \min(x|y,z)\ge z=\max(x\&y,z)\)。
由此繼續推廣,得上述結論,證畢。
-
因此根據上述結論我們可以先求出最大答案,接下來處理字典序的問題。
從前往後推,若該位填
|
,後面的依然按照結論進行,若答案不變,則本位可以填|
,否則填&
。由此 \(O(n^2)\) 演算法已經可以得到 \(60pts\) 了。
考慮 \(a_i<2^{60}\) ,可以按位計算,當計算到第 \(i\) 位時,對於區間 \([l,r]\),若填
&
,只有 \(a_l\sim a_r\) 本位均為 \(1\) 時答案本位才能為 \(1\),否則為 \(0\);若填|
,只要 \(a_l\sim a_r\) 中有一個本位為 \(1\) 答案本位就可以為 \(1\),否則為 \(0\)。由此演算法最佳化到 \(O(n\log(n))\),可以透過此題。
點選檢視程式碼
#include<bits/stdc++.h> #define int long long #define endl '\n' #define sort stable_sort using namespace std; const int N=2e5+10,M=61; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,k,a[N],maxx,m,ans,now,b[M],sum[N][M]; int check(int l,int now,int k) { if(l<=n-k) { for(int i=0;i<=m;i++) b[i]=sum[n-k][i]-sum[l-1][i]; for(int i=0;i<=m;i++) if(b[i]!=n-k-l+1&&(now>>i)&1) now^=(1ll<<i); } if(n-k+1<=n) { for(int i=0;i<=m;i++) b[i]=sum[n][i]-sum[n-k][i]; for(int i=0;i<=m;i++) if(b[i]!=0) now|=(1ll<<i); } return now; } signed main() { read(n),read(k); for(int i=1;i<=n;i++) read(a[i]),maxx=max(maxx,a[i]); m=log2(maxx); for(int i=1;i<=n;i++) { for(int j=0;j<=m;j++) sum[i][j]=sum[i-1][j]; for(int j=0;j<=m;j++) if((a[i]>>j)&1) sum[i][j]++; } ans=check(2,a[1],k); write(ans),puts(""); now=a[1]; for(int i=2;i<=n;i++) if(k>=1&&check(i+1,now|a[i],k-1)==ans) putchar('|'),now|=a[i],k--; else putchar('&'),now&=a[i]; }
-
T4
-
賽時部分分都沒打出來,直接看正解吧。
-
正解:
預處理一個布林陣列 \(ok_{l,r}\),表示區間 \([l,r]\) 能否全部刪掉。
區間 \(DP\) 處理,有:
\[ok_{l,r}|=(ok_{l,k-1}\&ok_{k+1,r})\&(\gcd(a_{l-1},a_k)|\gcd(a_k,a_{r+1})),k∈[l,r] \]初始值: \(\begin{cases} ok_{l,r}=1&l>r\\ ok_{i,i}=1&\gcd(a_{i-1},a_i)>1 || \gcd(a_i,a_{i+1})>1\\ \end{cases}\)
此時需要假設 \(a_{l-1},a_{r+1}\) 沒有被刪掉,貌似假了,但是看到下面的 \(DP\) 式子就知道其是正確的了。
\(f_i\) 表示若 \(a_i\) 不刪,前 \(i\) 個元素中最多能刪多少個,有:
\[f_i=\max_{j=0}^{i-1}\{f_j+ok_{j+1,i-1}\times (i-j-1)\} \]答案為 \(f_{n+1}\)。
預處理 \(\gcd\),複雜度為 \(O(n^3)\)。
點選檢視程式碼
#include<bits/stdc++.h> #define int long long #define endl '\n' #define sort stable_sort using namespace std; const int N=510; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,a[N],gcd[N][N],f[N]; bool ok[N][N]; int Gcd(int a,int b) {return b==0?a:Gcd(b,a%b);} signed main() { read(n); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) gcd[j][i]=gcd[i][j]=Gcd(a[i],a[j]); for(int i=1;i<=n;i++) for(int j=1;j<i;j++) ok[i][j]=1; for(int i=1;i<=n;i++) if(gcd[i][i+1]>1||gcd[i-1][i]>1) ok[i][i]=1; for(int i=1;i<=n;i++) for(int l=1;l+i-1<=n;l++) { int r=l+i-1; for(int k=l;k<=r;k++) { ok[l][r]|=(ok[l][k-1]&ok[k+1][r])&(gcd[k][l-1]>1|gcd[k][r+1]>1); if(ok[l][r]==1) break; } } for(int i=1;i<=n+1;i++) for(int j=0;j<=i-1;j++) f[i]=max(f[i],f[j]+(i-j-1)*ok[j+1][i-1]); write(f[n+1]); }