1. 程式人生 > >[hgoi#2019/3/3]賽後總結

[hgoi#2019/3/3]賽後總結

累加 安全 串處理 定義 sum air sca 程序 整數

技術分享圖片

T1--最長公共前綴(lcp)

定義兩個字符串S,T 的最長公共前綴lcp(S,T)為最長的字符串R,滿足R 既是S 的前綴又是T 的前綴。
給定一個字符串S,下標從1 開始,每次詢問給出四個正整數a,b,c,d,你需要輸出[a,b]這個子串與[c,d]這個子串的lcp 的長度。

解法

暴力60分不用說了,那麽正解可以用擴展\(kmp\),或者是萬能字符串處理方法\(hash\)。但是我不會擴展\(kmp\),所以我就只用\(hash\)來亂搞一下。

那麽二分查找這個前綴的長度,每次用\(hash\)檢查一下,總的時間復雜度就是\(O(nlogn)\)

ac代碼

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
# define N 100005
const int Mod=1e9+7,Base=127;
LL f[N],hash[N];
int n,m;
char s[N];
LL get_hash(int l,int r){
    return (f[r]-f[l-1]*hash[r-l+1]%Mod+Mod)%Mod;
} 
int query(int a,int b,int c,int d){
    int l=1,r=b-a+1,res=0;
    if (d-c+1<r) r=d-c+1;
    while (l<=r){
        int mid=(l+r)>>1;
        if (get_hash(a,a+mid-1)==get_hash(c,c+mid-1)) l=mid+1,res=mid;
        else r=mid-1;
    }
    return res;
}
int main(){
    n=gi(),m=gi();
    scanf("%s",s+1); 
    hash[0]=1; 
    for (int i=1;i<=n;i++) hash[i]=hash[i-1]*(LL)Base%Mod;
    for (int i=1;i<=n;i++) f[i]=(f[i-1]*(LL)Base+s[i])%Mod;
    while (m--){
        int a=gi(),b=gi(),c=gi(),d=gi();
        printf("%d\n",query(a,b,c,d));
    }
    return 0;
}

T2--搶救糧倉(save)

查爾明的家著火啦!他的糧倉正面臨著被燒毀的危險!

查爾明在山上一共建有n 個糧倉,按海拔從高到低依次被標記上編號1 到n,且沒有任意兩個糧倉的海拔是一樣的。第i 個糧倉裏藏有a[i]噸糧食,第i 個糧倉與第i-1 個糧倉之間的距離為1。

查爾明立馬掏出水槍進行滅火,每次滅火他可以指定一個糧倉進行搶救,由於不同糧倉的火勢不同,需要噴的水量也不同,搶救第i 個糧倉需要消耗b[i]噸水。查爾明的手速實在是太快了,你可以理解成滅火是瞬間完成的。與此同時,查爾明的助手們開著貨車去轉移糧食,因為他們認為越低的地方越安全,所以他們只會把糧食從高處運往低處,最終運到某個被滅過火的糧倉。查爾明的糧食很奇怪,每個糧倉都有一個共享值d[i],表示第i 個糧倉只能容納與距自己距離不超過d[i]的糧倉的糧食,否則就會發生爆炸,所以在運輸的過程中務必要避免發生爆炸。

已知水費和糧食運輸費都為1 元/噸,查爾明想知道,自己最少需要花費多少錢來搶救全部糧食?

解法

一開始沒有想到用dp,就打了一個三個特判。當\(n<=1000\)時,按照從低到高依次遍歷,如果可以直接滅掉,那麽就直接滅掉,然後再用這個點來更新後面的其他節點,我們更新的就是最小值,在判斷。第二種情況是\(d_i=n\),那麽也就是所有的節點都可以更新其他的所有節點,那麽就暴力取最小值。

但是這只是\(70\)分,那麽如果要拿到接下來的30分,我們就需要用\(dp\)\(f[i]\)表示前\(i\)個倉庫需要的最小代價。轉移方程就是:\(f[i]=min(f[j]-sum[j])+sum[i-1]+b[i](j>=i-d[i]-1)\)

,這個意思就是可以從\(j\),這個j是在i的範圍內,加上他能夠帶來的代價,再取上最小值。

這個最小值可以用單調隊列來維護或者是用樹狀數組或者是線段樹來維護。

ac代碼

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# include <vector>
# include <queue>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
# define N 100005
int q[N],f[N],a[N],b[N],d[N],sum[N];
int n,tail,head;
int find(int x){
    int l=head,r=tail-1,res;
    while (l<=r){
        int mid=(l+r)>>1;
        if (q[mid]<x) l=mid+1;
        else r=mid-1,res=mid;
    }
    return q[res];
}
int main(){
    n=gi();
    for (int i=1;i<=n;i++) a[i]=gi(),sum[i]=sum[i-1]+a[i];
    for (int i=1;i<=n;i++) b[i]=gi();
    for (int i=1;i<=n;i++) d[i]=gi();
    q[tail++]=0;
    for (int i=1;i<=n;i++){
        int p=find(i-d[i]-1);
        f[i]=f[p]-sum[p]+b[i]+sum[i-1];
        while (head<tail&&f[q[tail-1]]-sum[q[tail-1]]>=f[i]-sum[i]) tail--;
        q[tail++]=i;
    }
    printf("%d\n",f[n]);
    return 0;
}

T3--幸運7(seven)

查爾明為了鍛煉自己的觀察力,在紙上畫了一棵有n 個節點的樹,點與點之間通過邊連接,每條邊有一個正整數權值。查爾明想要用肉眼觀察出有多少組(i,j)滿足1<=i<j<=n,且i 到j 的距離是他的幸運數7 的倍數。

查爾明不一會兒就觀察出來了,但是他不知道自己的答案是否正確,你能寫一個程序幫助他檢驗自己的答案嗎?

解法

這道題太水了,原題是聰聰可可,點分治或者是樹形dp。

點分治在我的xio講堂裏有講到過,講一下如何用樹形dp來做。

定義:\(f[i][j]\)表示以第\(i\)為根節點的子樹,到根節點距離除以7的余數\(j\)的節點的個數。

轉移方程就是:\(f[i][j]=f[son][((j-len)%7+7%7]\)\(son\)\(i\)節點的兒子,\(len\)\(i\)\(son\)之間的長度。

答案\(ans\)需要在遞歸的時候累加出來,\(ans=f[son][(-j-len)%7]*f[u][j]\),。

ac代碼

# include <cstdio>
# include <cstring>
# include <algorithm>
# include <ctype.h>
# include <iostream>
# include <cmath>
# include <map>
# define LL long long
# define ms(a,b) memset(a,b,sizeof(a))
# define ri (register int)
# define inf (0x7f7f7f7f)
# define pb push_back
# define fi first
# define se second
# define pii pair<int,int>
using namespace std;
inline int gi(){
    int w=0,x=0;char ch=0;
    while(!isdigit(ch)) w|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return w?-x:x;
}
# define N 100005
struct edge{
    int to,nt,w;
}E[N<<1];
LL f[N][7];
int H[N];
int cnt,n;
LL ans;
void addedge(int u,int v,int w){
    E[++cnt]=(edge){v,H[u],w}; H[u]=cnt; 
}
void dfs(int u,int fa){
    f[u][0]=1;
    for (int e=H[u];e;e=E[e].nt){
        int v=E[e].to,w=E[e].w;
        if (v==fa) continue;
        dfs(v,u);
        for (int i=0;i<7;i++) ans+=f[v][i]*f[u][((-i-w)%7+7)%7]*2;
        for (int i=0;i<7;i++) f[u][((i+w)%7+7)%7]+=f[v][i];
    }
}
int main(){
    n=gi();
    for (int i=1;i<n;i++){
        int u=gi(),v=gi(),w=gi();
        addedge(u,v,w); addedge(v,u,w);
    }
    ans=0; dfs(1,0);
    printf("%lld\n",ans/2);
    return 0;
}

[hgoi#2019/3/3]賽後總結