dp round again
A
發現構造若干個 \(a\) 然後接若干個 \(e\) 接若干個 \(i\) 接若干個 \(o\) 再接若干個 \(u\) 且讓這些字母的出現次數儘量相等最優。直接構造時間複雜度為 \(O(n)\)。
void solve(unsigned __testid=1){
int n;
cin>>n;
F(i,0,4){
int cnt=n/5;
if(n%5>i)++cnt;
F(j,1,cnt){
if(i==1)putchar('e');
if(i==2)putchar('i');
if(i==3)putchar('o');
if(i==4)putchar('u');
if(!i)putchar('a');
}
}
cout<<'\n';
}
B1
分類討論 \(c\) 在 \(b_1\) 左、\(b_1\) 和 \(b_2\) 之間,\(b_2\) 右三種情況討論。
void solve(unsigned __testid=1){
int n,m,q;cin>>n>>m>>q;
int b1,b2;cin>>b1>>b2;int c;cin>>c;
if(b1>b2)swap(b1,b2);
if(b1<=c&&c<=b2)cout<<(b2-b1)/2<<'\n';
else if(c<b1)cout<<b1-1<<'\n';
else cout<<n-b2<<'\n';
}
B2
在 B1 的基礎上加一個二分求出 \(c\) 在 \(b\) 的哪一段區間即可。時間複雜度為 \(O(n+q\log n)\)。
int a[N],b[N];
void solve(unsigned __testid=1){
int n,m,q;cin>>n>>m>>q;
F(i,1,m)cin>>b[i];
sort(b+1,b+m+1);
F(i,1,q){
int x;cin>>x;
int l=1,r=m,best=-1;
if(x<b[1])cout<<b[1]-1<<'\n';
else if(x>b[m])cout<<n-b[m]<<'\n';
else{
while(l<=r){
int mid=l+r>>1;
if(b[mid]>=x)best=mid,r=mid-1;
else l=mid+1;
}
assert(~best);
// best-1 ~ best
cout<<(b[best]-b[best-1])/2<<'\n';
}
}
}
C
咚咚咚
一個十分複雜的做法。
設 \(f_{i,0/1/2/3/4}\) 表示第 \(i\) 個字串以 \(n/a/r/e/k\) 開頭最多可以獲得多少得分。
首先可以發現字串 \(s_i\) 中不是 \(n/a/r/e/k\) 的字元對答案毫無貢獻,因此先刪去。
然後進入 dp。首先設 \(f_{0,4}=0\) 為初始狀態。
然後考慮做轉移方程。
對於每一個字串 \(s_i\) 和其開始字元 \(j\),都暴力掃描字串 \(s_i\) 得到最後一個出現的位置 \(p\)。然後用 \(f_{i,p}\) 更新 \(f_{k,\text{Pre}(j)}\)(其中 \(\text{Pre}(j)\) 表示 \(j\) 迴圈的上一個字元是什麼)。令 \(cnt\) 表示當前可以被匹配上的字元數,\(all\) 表示為 \(n/a/r/e/k\) 但是當前不能被匹配上的字元數。更新的時候:
- 考慮 \(\text{Pre}(j)=4\),此時可以直接從這個字串打頭,此時這個字串對答案的貢獻為 \(\lfloor\frac{cnt}{5}\rfloor\times 5-cnt\bmod 5-all\)。
- 列舉上一個被選擇的字串 \(s_k\),此時對答案的貢獻為:
- 若 \(s_i\) 在 \(s_k\) 的基礎上開闢了一個新的迴圈,那麼:
- 若 \(\text{Pre}(j)=4\),則此時 \(s_k\) 最後一個迴圈已經被匹配,因此對答案的貢獻為 \(0\)。
- 若 \(\text{Pre}(j)\neq 4\),則此時 \(s_k\) 最後一個迴圈沒有被匹配,計算該迴圈剩下的未匹配字元數量 \(4-\text{Pre}(j)\),其就是對答案的貢獻。
- 否則,因為還在上一個迴圈裡所以對答案沒有貢獻。
- 計算 \(s_k\) 所屬完整迴圈節數量 \(\lfloor\frac{cnt-4+\text{Pre}(j)}{5}\rfloor\times 5\) 也是對答案的貢獻。
- 剩下的 \((cnt-4+\text{Pre}(j))\bmod 5\) 即為下一個迴圈但是沒有被 \(narek\) 字串所完全覆蓋的長度,對答案的貢獻為 \(-((cnt-4+\text{Pre}(j))\bmod 5)\)。
- 若 \(s_i\) 在 \(s_k\) 的基礎上開闢了一個新的迴圈,那麼:
最後的答案即為 \(\max f_{i,j}\)。
考慮依據上述轉移方程暴力轉移,時間複雜度為 \(O(n^2)\)。
void solve(unsigned __testid=1){
// freopen(".out","w",stdout);
int n,k;cin>>n>>k;
F(i,1,n)cin>>s[i];
F(i,1,n){
string t;
for(auto &j:s[i])
if(j=='n'||j=='a'||j=='r'||j=='e'||j=='k')
t+=j;
s[i]=t;
}
F(i,1,n){
len[i]=s[i].size();
s[i]=' '+s[i];
}
F(i,1,n)
F(j,0,4)
f[i][j]=-1e18;
int mx=0;
f[0][4]=0;
F(i,1,n){
char sp[]={'n','a','r','e','k'};
F(j,0,4){
int cnt=0,all=0;
int now=j;
F(k,1,len[i]){
if(s[i][k]==sp[now]){
now=(now+1)%5;
++cnt;
}else ++all;
}
if(!cnt)continue;
// cout<<"i=" <<i<<" then j can be "<<j<<", "<<cnt<<' '<<all<<'\n';
int pre=now-1;
if(pre<0)pre+=5;
now=pre;
pre=j-1;
if(pre<0)pre+=5;
if(pre==4)f[i][now]=max({cnt/5*5-all-cnt%5,f[i][now]});
F(k,1,i-1){
int cost=f[k][pre];
int cntt=cnt;
if(cnt<4-pre)cost-=cnt+all;
else{
if(pre!=4){
cnt-=(4-pre);
cost+=5-all;
cost+=pre+1;
}else cost-=all;
cost+=cnt/5*5;
cost-=cnt%5;
}
cnt=cntt;
// cout<<"WORI "<<k<<' '<<cnt<<' '<<cost<<' '<<now<<' '<<pre<<' '<<f[k][pre]<<" COMPARE "<<(cnt<4-pre)<<'\n';
f[i][now]=max(f[i][now],cost);
}
}
F(j,0,4)mx=max(mx,f[i][j]);
// cout<<"qwq "<<i<<' ';
// F(j,0,4)cout<<f[i][j]<<' ';
// cout<<'\n';
}
cout<<mx<<'\n';
}
D
咕咕咕。
E1
一個很 trival 的思路是設 \(f_{i,j,k}\) 表示當前匹配到 \(a\) 中第 \(i\) 個字元,選擇到 \((j,k)\) 位置是否有必勝策略。
那麼考慮列舉 \((p,q)\) 位置表示列舉第 \(i+1\) 個位置選擇 \((p,q)\) 是否有必勝策略。那麼 \(f_{i,j,k}\) 為 \(0\) 當且僅當所有合法的 \((p,q)\) 均滿足 \(f_{i+1,p,q}=1\),否則 \(f_{i,j,k}\) 為 \(1\)。
時間複雜度為 \(O(n^5)\),但是發現每一次 \(O(n^2)\) 在連續矩陣中找答案所以考慮用 2D-字首和最佳化,時間複雜度最佳化為 \(O(n^3)\) 可以透過。
比 C 簡單不知道多少。
int f[310][310][310],b[310][310],a[310310];
void solve(unsigned __testid=1){
int l,n,m;cin>>l>>n>>m;
F(i,1,l)cin>>a[i];
F(i,1,n)F(j,1,m)cin>>b[i][j];
F(i,0,l+1)F(j,0,n+1)F(k,0,m+1)f[i][j][k]=0;
G(i,l,1)G(j,n,1)G(k,m,1){
if(a[i]==b[j][k])f[i][j][k]|=!f[i+1][j+1][k+1];
f[i][j][k]=f[i][j][k]+f[i][j+1][k]+f[i][j][k+1]-f[i][j+1][k+1];
}
if(f[1][1][1])cout<<"T\n";
else cout<<"N\n";
}
E2
咕咕咕。