10.9離線賽

一、雙擊
數據:對於60%,n、m∈[1,5000]
對於100%,n、m∈[1,500000]

對於60%,N*M的很好想,對於每個詢問點,向前一直和向後一直找就行了。
然後可以想到其實對於這一塊裏所有答案都是一樣的,那就可以預處理出來,把每一塊的L、R存下來,放在兩個數組裏。詢問時在R數組裏二分查找。
再者,可以用並查集,把一塊裏其他點都指向第一個點,把這一塊的L、R座標放在第一個點上。詢問的時候就去找執行的那個點就行了,這樣只有N了

#include<bits/stdc++.h>
using namespace std;
void Rd(int &res){
    res=0;char c;
    while(c=getchar(),c<48);
    do res=(res<<3)+(res<<1)+(c&15);
    while(c=getchar(),c>=48);
}
char str[500005];
struct node{int L,R;}A[500005];
int fa[500005];
int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
int op(char c){return c>='0'&&c<='9';}
int main(){
    int n,m;
    Rd(n);Rd(m);
    scanf("%s",str);
    for(int i=0;i<n;i++)fa[i]=i;
    int L=0,R=0;
    for(int i=0;i<n;i++){
        if(op(str[i])==op(str[L])){R=i;fa[i]=L;}
        else{//把L、R位置存起來
            A[L].L=L;A[L].R=R;
            L=R=i;
        }
    }
    A[L].L=L;A[L].R=R;//最後一個L、R要單獨存
    fa[R]=L;
    for(int i=1;i<=m;i++){
        int x;
        Rd(x);
        int y=Find(x);
        printf("%d %d\n",A[y].L,A[y].R);
    }
    return 0;
}

二、取子串
數據:對於30%,lenS∈[1,10],lenT=1
對於100%,lenS∈[1,1e5],lenT∈[1,1e5]

一看就是dp題,難點有兩個。

1)能否快速判斷這個字串是不是T
2)怎麼轉移方案數

對於1,可以Hash一下,把S、T都弄出來,然後見一下就行了,O(N)的預處理。
對於2,要細講。
定義dp[i]爲以i位置爲末時的方案數;
每次對於一個i,有兩種情況。一個是不和前面的匹配成T,另一個是和前面的合併變成T。
對於第一個,只要每次和前面的所有T合併就行了,相當於把一個字母跟在前面的一個T後面,那就是前面的所有方案又被弄了一次,那dp[i]=dp[i-1]
對於第二個,如果合併成了T,也有兩種情況,一種是T自己這一塊,有i-m+1的情況(就是把1到i-m的點都依次併到這邊來),然後是T和前面的方案數去合併。圖示如下:
這裏寫圖片描述
這裏的dp是以i爲末尾是的情況,不要弄錯了

#include<bits/stdc++.h>
#define Mod 1000000007
#define M 100005
#define P 233
using namespace std;
char S[M],T[M];
int n,m,dp[M],cnt[M],sum[M];
unsigned long long H[M],HT,base[M];
bool judge(int x){return H[x]-H[x-m]*base[m]==HT;}
int main(){
    scanf("%s %s",S+1,T+1);
    n=strlen(S+1),m=strlen(T+1);

    base[0]=1;
    for(int i=1;i<=n;i++)base[i]=base[i-1]*P;
    for(int i=1;i<=n;i++)H[i]=H[i-1]*P+S[i]-'a';
    for(int j=1;j<=m;j++)HT=HT*P+T[j]-'a';
    //這一塊是hash,字符串匹配,要用unigsed long long,不然會超

    for(int i=1;i<=n;i++){
        if(judge(i)){//第二種情況
            dp[i]=(dp[i]+i-m+1)%Mod;
            //for(int j=1;j<=i-m;j++)dp[i]=(dp[i]+cnt[j])%Mod;
            //這裏是不用前綴和的,用來解釋sum這個前綴和,前綴和的前綴和
            dp[i]=(dp[i]+sum[i-m])%Mod;
        }
        else dp[i]=dp[i-1];//第一種情況
        cnt[i]=(cnt[i]+cnt[i-1]+dp[i])%Mod;//dp的前綴和
        sum[i]=(sum[i]+sum[i-1]+cnt[i])%Mod;//sum的前綴和
    }
    printf("%d\n",cnt[n]);//輸出的是dp[1--n]的和,即cnt[n]
    return 0;
}

三、Paths
數據:對於30%,n、m∈[1,20]
對於100%,n、m∈[1,100000]

對於60%,二進制枚舉很簡單,把每條路徑上每個點標記掉,然後再選。

對於100%,貪心就能過。
對於一條鏈,把下標重新賦值後,就是看電視一樣的貪心。
放到樹上呢,其實是一樣的。按照LCA從大到小排個序後,每次先選深度大的,然後能選就選。正確性顯然,兩條路徑,若相交,那麼肯定選LCA深度大的,這樣影響到的點就少。還有選了這個路徑,影響到的僅是LCA比他大的,那就很明顯是對的了。然後再標記掉整棵以此LCA爲根的子樹,避免相交。

題外話:判斷兩條路徑相交
對於兩條路,較淺的LCA的那條路的兩個端點走到與較深的LCA同深度時,若有一個點和LCA相同,那兩條路徑就相交

#include<bits/stdc++.h>
#define M 100005
using namespace std;
int n,m,fa[18][M],dep[M];
vector<int>edge[M];
struct node{int a,b,lca;}A[M];
void f(int x,int fa1){//造樹
    fa[0][x]=fa1;dep[x]=dep[fa1]+1;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1)continue;
        f(y,x);
    }
}
void Init(){//倍增預處理
    for(int j=1;j<18;j++)
        for(int i=1;i<=n;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]];
}
int LCA(int x,int y){//求LCA
    if(dep[x]>dep[y])swap(x,y);
    int step=dep[y]-dep[x];
    for(int i=0;i<18;i++)
        if(step&(1<<i))y=fa[i][y];
    if(x==y)return x;
    for(int i=17;i>=0;i--)
        if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
    return fa[0][x];
}
bool cmp(node x,node y){return dep[x.lca]>dep[y.lca];}//按照LCA深度排序
bool Q[M];
void f1(int x,int fa1){//標記掉以這個LCA爲根的子樹
    Q[x]=1;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i];
        if(y==fa1||Q[y])continue;//避免重複標記
        f1(y,x);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    f(1,0);
    Init();
    for(int i=1;i<=m;i++){
        scanf("%d%d",&A[i].a,&A[i].b);
        A[i].lca=LCA(A[i].a,A[i].b);
    }
    sort(A+1,A+1+m,cmp);
    int ans=0;
    for(int i=1;i<=m;i++){//貪心
        if(Q[A[i].a]||Q[A[i].b])continue;
        ans++;
        f1(A[i].lca,fa[0][A[i].lca]);
    }
    printf("%d\n",ans);
    return 0;
}

樹的題可以先從鏈想,然後上升一個高度到樹,其實差不多

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