CF Good Bye 2020 題解&總結 A~G
眾所周知這種手速場是掉分好時機。
前200人均切7題,由於我沒有切G導致400+。
不過竟然沒有掉分真是令人震驚呢。
血的教訓:
以後都用萬能標頭檔案。
A
求兩兩之間的差不重複的有多少個。
範圍小,暴力即可。
B
比較顯然的貪心:丟進桶中,從大往小做。如果當前位置大於等於\(2\),後面位置等於\(0\),就分一個到後面去。
C
卡題了。雖然切了但是血虧。
發現\(O(26^2n)\)的DP會MLE,所以把狀態改成:\(f_{i,0/1,0/1}\),後兩個狀態表示後面兩個位置是否被改過。
如果兩個位置都改過了,那麼可以認為兩個位置不同。因為字符集大小比較大,所以肯定存在方案調整使得兩個位置不同,並且滿足其它的不等關係。
D
顯然每個聯通塊一定是連續的;否則可以只保留那個比較大的連通塊。
考慮一個點\(x\)的貢獻為它所屬的不同連通塊個數。顯然最多屬於\(deg_x\)個。
可以發現把一個連通塊分成兩個連通塊,交界的點只有一個。
增量法搞。一開始只有一個連通塊,將所有\(deg_x\)減一。每次找到\(deg_x\)不為\(0\)的權值最大的點\(x\),將其權值加入答案並把\(deg_x\)減一。
E
由於ll事先看了下發現它很水提醒了我們,所以在切A之後立即切了E。
顯然可以列舉\(j\),分別算\(i\)和\(k\)的貢獻和乘起來。
計算貢獻的時候可以先預處理對於每一位,這一位上是\(1\)的數有多少個。
F
卡題了,還掛了4次,血虧。
可以抽象成一個圖。先不考慮自環,則最終形成的圖中,如果出現環,那麼環上的一條邊可以被其它邊替代;所以最終形成的圖是個森林。考慮自環,如果一棵樹中存在一個自環,那麼樹中的每個節點都可以任意調整。
按順序加邊。加入一個自環時,如果樹中沒有自環就加入;加入一條普通邊時,如果不在同個連通塊,並且不是兩個連通塊中都有自環,那麼就加入。實現的時候搞個超級點\(0\),連自環的時候和\(0\)連邊,連普通邊的時候直接判斷是否在同個連通塊中。
G
最上面的那張圖已經清楚地表明瞭一切。
先將字串擴充套件成最小的\(s_i\),使剛好滿足\(|s_i|\ge |w|\)。然後答案分成兩部分:\(s_i\)
前者暴力搞。對於後者,分別找\(lmx,rmx\),\(lmx\)表示最長的\(w\)的字首匹配\(s_i\)的字尾的長度,即\(w[1..lmx]=s_i[|s_i|-lmx+1..|s_i|]\)。\(rmx\)類似。可以強行雜湊或exkmp搞出來。
分別處理正反串kmp的陣列\(p_i,q_i\),分別以此建樹。設\(x\)和\(y\)為貢獻時的第一個\(s_i\)的字尾和第二個\(s_i\)的字首的長度,如果\(x+y+1=n\),\(w[x+1]=t[j]\),\(lmx\in subtree_p(x),rmx\in subtree_q(y)\),那麼這組\((x,y)\)就可以貢獻到。由於詢問的\((lmx,rmx)\)只有一個,\(t[j]\)只有\(26\)種不同的取值,直接\(O(|w|)\)處理出來每個取值是什麼時的答案。最後統計一下\(t_j\)相同時的貢獻之和,隨便計算一下即可。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 1000005
#define ll long long
#define mo 1000000007
ll qpow(ll x,ll y=mo-2){
ll r=1;
for (;y;y>>=1,x=x*x%mo)
if (y&1)
r=r*x%mo;
return r;
}
int n,m,Q;
char s0[N],t[N];
char w[N],v[N];
int k,len;
int p[N],q[N];
char s[N*4],s_[N*4];
int ns;
int pw[N*4];
int bf(){
int res=0;
for (int i=1,j=0;i<=ns;++i){
while (j && w[j+1]!=s[i])
j=p[j];
if (w[j+1]==s[i]){
++j;
if (j==len)
res++,j=p[j];
}
}
return res;
}
ll ans;
int lmx,rmx;
#define mo2 1000000009
struct hsh{
int x,y;
};
hsh operator+(hsh a,hsh b){return {(a.x+b.x)%mo,(a.y+b.y)%mo2};}
hsh operator+(hsh a,int b){return {(a.x+b)%mo,(a.y+b)%mo2};}
hsh operator*(hsh a,int b){return {(ll)a.x*b%mo,(ll)a.y*b%mo2};}
bool eql(hsh a,hsh b){return a.x==b.x && a.y==b.y;}
int mxsuc(char w[],char s[]){
int res=0;
hsh hs,hw,p;
hs=hw={0,0};
p={1,1};
for (int i=ns,j=1;i>=1 && j<=len;--i,++j){
hs=hs*26+(s[i]-'a');
hw=hw+p*(w[j]-'a');
if (eql(hs,hw))
res=j;
p=p*26;
}
return res;
}
void init(){
memcpy(s_,s,sizeof(char)*(ns+1));
memcpy(v,w,sizeof(char)*(len+1));
reverse(s_+1,s_+ns+1);
reverse(v+1,v+len+1);
q[1]=0;
for (int i=2,j=0;i<=len;++i){
while (j && v[j+1]!=v[i])
j=q[j];
if (v[j+1]==v[i])
++j;
q[i]=j;
}
lmx=mxsuc(w,s);
rmx=mxsuc(v,s_);
}
int anc[N],buc[26];
vector<int> tc[26],ts[26];
int sum[26];
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
pw[0]=1;
for (int i=1;i<=4000000;++i)
pw[i]=pw[i-1]*2%mo;
scanf("%d%d%s%s",&n,&Q,s0+1,t+1);
m=strlen(s0+1);
for (int i=0;i<26;++i){
tc[i].push_back(0);
ts[i].push_back(0);
}
for (int i=1;i<=n;++i){
tc[t[i]-'a'].push_back(i);
(sum[t[i]-'a']+=qpow(2,mo-1-i))%=mo;
ts[t[i]-'a'].push_back(sum[t[i]-'a']);
}
while (Q--){
scanf("%d%s",&k,w+1);
len=strlen(w+1);
p[1]=0;
for (int i=2,j=0;i<=len;++i){
while (j && w[j+1]!=w[i])
j=p[j];
if (w[j+1]==w[i])
++j;
p[i]=j;
}
ns=m;
for (int i=1;i<=ns;++i)
s[i]=s0[i];
ans=0;
int i=0;
for (;i<k && ns<len;){
++i;
s[ns+1]=t[i];
for (int i=1;i<=ns;++i)
s[ns+1+i]=s[i];
ns=ns*2+1;
}
(ans+=(ll)bf()*pw[k-i])%=mo;
if (i==k){
printf("%lld\n",ans);
continue;
}
init();
memset(anc,0,sizeof(int)*(len+1));
for (int x=rmx;1;x=q[x]){
anc[x]=1;
if (x==0)
break;
}
memset(buc,0,sizeof buc);
for (int x=lmx;1;x=p[x]){
if (anc[len-1-x])
buc[w[x+1]-'a']++;
if (x==0)
break;
}
for (int j=0;j<26;++j){
int p=upper_bound(tc[j].begin(),tc[j].end(),k)-tc[j].begin()-1;
int q=upper_bound(tc[j].begin(),tc[j].end(),i)-tc[j].begin()-1;
(ans+=(ll)(ts[j][p]-ts[j][q])*buc[j]%mo*pw[k])%=mo;
}
ans=(ans+mo)%mo;
printf("%lld\n",ans);
}
return 0;
}
H
沒看題意。
I
沒看題意。
感覺這種比賽一卡題人就沒了。
前6題人均AC。唯一有點區分度的就是G題,後面的題都是在神仙打架了。
這次最虧的大概就是F沒想好就掛了4次和G題標頭檔案沒有寫全了。