集訓模擬賽3【啥也不會的一天】

Vocanda發表於2020-06-29

前言

今天\(T1\)快速冪(可惜我假期摸魚……)\(T2\)是字典樹(正解其實應該是AC自動機,但是沒人會……)\(T3\)竟然整了一個雜湊dp……當場自閉。\(T4\)多種方法,最短路,暴力dp,線段樹均可。總的來說就是自閉的一天……(說實話,今天老師選的題真的是開玩笑一樣)

NO.1 李時珍的皮膚衣

題目描述

\(LSZ\)很皮!\(LSZ\)的皮膚衣更皮!

\(LSZ\)有很多件神奇的皮膚衣,而且\(LSZ\)總是喜歡一次穿上多件皮膚衣(一件套一件,而且一直穿好多天),這些皮膚衣有透明或不透明兩種狀態,當不透明的皮膚衣吸收了一天的陽光直射後,就會變成透明的皮膚衣,透明的皮膚衣能使陽光照射到裡層皮膚衣,而透明的皮膚衣再吸收陽光,會在第二天會變成不透明的皮膚衣,不透明的皮膚衣會阻止陽光照射到裡層皮膚衣。

\(LSZ\)從某天起(該天算作第\(1\)天)穿上\(N\)件皮膚衣(剛開始所有皮膚衣都是不透明的),問你最少要經過多少天,\(LSZ\)身上的皮膚衣都經歷過透明變化?

例如今天(公元\(2018\)\(6\)\(17\)日)\(LSZ\)穿了\(3\)件皮膚衣服,會在公元\(2018\)\(6\)\(21\)\(3\)件皮膚衣都會經歷過透明變化。

輸入格式

一行,只有一個整數\(N\)

輸出格式

最少的天數(對\(N\)取餘數)

樣例

樣例輸入1

3

樣例輸出1

2

樣例1解釋

使用\(0\)代表皮膚衣透明狀態。

使用\(1\)代表皮膚衣不透明的狀態。

\(5\)\(3\)取餘數為\(2\)

樣例輸入2

5

樣例輸出2

2

資料範圍與提示

\(20\%\)的資料:\(N \le 15\)
\(60\%\)的資料:\(N \le 10^7\)
\(100\%\)的資料:\(N \le 10^{10}\)

分析

看到這個題目一開始是沒有思路的,但是可以手模一到兩個小資料,然後就會發現,其實這個答案是有規律的。假設\(i\)件,\(g[i]\)為答案,那麼手模幾個可以發現這個題的規律跟漢諾塔差不多,即\(g[i] = 2\times g[i-1]+1\),相信能做這種題的數列都學過,推個通項公式嚶該沒問題(好懶……),最後可以推出來答案應該是\(2^n-1\)
但是看到資料範圍,\(N \le 10^{10}\),開玩笑??所以我們需要快速冪並取模。其實就是個板子,推出來公式就很好寫了。

程式碼

#include<bits/stdc++.h>
#define ll unsigned long long
ll n;
ll pow(ll a,ll b){//注意如果using namespace std了,並且用的萬能庫,函式名千萬別用pow,有衝突但不報錯,我就是這麼掛的……
    ll ans = 1;//這塊就是快速冪板子
    ll base = a%n;
    while(b){
        if((b & 1) !=0){
            ans = (ans*base)%n;
        }
        base = (base*base)%n;
        b>>=1;
    }
    return ans;
}
int main(){//求值,注意過程取模
	std::cin>>n;
	ll ans = pow(2,n-1);
	ll jl = (ans+1)%n;
	std::cout<<jl<<std::endl;
}

NO.2 馬大嘴的廢話

題目描述

\(MZH\)愛說廢話,喜愛“水群”,經常被“提走”,管理員對\(MZH\)在群裡說話進行了限制:
1、只能說小寫英文字母。
2、長度不超過\(20\)
即使這樣,也不能阻止\(MZH\)“水群”,他在限制的條件下也說了成千上萬條廢話資訊,現在已知\(MZH\)說過的\(N\)條廢話資訊,接下來\(MZH\)要說\(M\)條廢話資訊,請你回答:對於他說的每條廢話資訊在已知\(N\)條廢話資訊中出現的次數和(如果一條中出現多次,只算一次)。

比如:\(MZH\)說過了\(3\)條資訊

1.adbdb

2.bcde

3.cdbf

4.db

現在說的資訊為db,那麼資訊db在資訊1和資訊3和資訊4出現過,所以答案為3.

輸入格式

第一行,一個整數N。

接下來\(N\)行,每行一個字串(只能由小寫字母組成),代表\(MZH\)說過的\(N\)條廢話資訊。

再接下來一行,一個整數\(M\)

接下來\(M\)行,每行一個字串(只能由小寫字母組成),代表當前\(MZH\)要說的一條廢話資訊。

輸出格式

\(M\)行,每行輸出該廢話資訊出現的次數和。

樣例

樣例輸入

20
ad
ae
af
ag
ah
ai
aj
ak
al
ads
add
ade
adf
adg
adh
adi
adj
adk
adl
aes
5
b
a
d
ad
s

樣例輸出

0
20
11
11
2

資料範圍與提示

\(20\%\)的資料:\(n\le 100 , m\le 50\)

\(60\%\)的資料:\(n\le 10^4, m\le 500\)

\(100\%\)的資料:\(n\le 10^4, m\le 10^5\)

分析

題意就是從上邊給出的字串中找出子串含有下邊字串的數量。暴力列舉掃描能過一半的點,後邊的就\(TLE\)了。那麼正解是什麼呢,我們考慮字典樹(\(Tire\)樹),其實正解應該是AC自動機,但是我沒學機房裡還有巨佬用的雙雜湊取模,(開了c++11使用unordered_map達到\(O(1)\)的查詢才過,不然會t。)對於我只是搞了搞字典樹。其中需要注意的是,因為查詢的是子串,所以我們需要把以最後一個字母為字尾的所有子串都存入,這樣才能達到全部能搜尋到,而且同一字串內到達一個節點時\(cnt\)不能加。然後就可以進行字典樹建樹查詢了。

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 26;
struct tree{//指標建立結構體。
        struct tree *son[26];//兒子指標
	int cnt;//記錄有多少情況到他
	int flag;//記錄是第幾個字串,也就是id
}*root;
void insert(char *p,int id){//建樹,字元取指標
	int i,k,j;
	tree *cur=root,*next;//初始化cur為根
        int len=strlen(p);
	for(i=0;i<len;i++){//依次列舉字元
		k=p[i]-'a';
		if(cur->son[k]!=NULL)//兒子不為空,就讓指標指向兒子
			cur=cur->son[p[i]-'a'];
		else {
			next=new(tree);//兒子為空就建一個新指標
			for(j=0;j<26;j++)//初始化
				next->son[j]=NULL;
			next->cnt=0;
			next->flag=-1;
			cur->son[p[i]-'a']=next;//讓空的兒子為下一個
			cur=next;//指標指向兒子
		}
		if(cur->flag!=id)//不是同一個字串就cnt++,更新id
		{
			cur->cnt++;	
			cur->flag=id;
		}
	}	
}

int Find(char *p){//查詢
    int i;
    tree *cur=root;
    int len = strlen(p);
    for(i=0;i<len;++i){
        int k=p[i]-'a';
            if(cur->son[k]==NULL){//子節點為空那麼肯定沒有該子串
                break;
            }
            else{
                cur=cur->son[k];//指標指向兒子
            }
    }
    if(i<len)return 0;//提前跳出則不符合要求
    return cur->cnt;//返回數目
}

int main(){
    int n,m;
    int i,j;
    char s[25];
    root = new(tree);
    for(int i=0;i<26;i++){
        root->son[i]=NULL;
    }
    root->cnt=0;
    root->flag=-1;
    scanf("%d",&n);
    for(i=1;i<=n;i++){//建樹
		scanf("%s",s);
		for(j=0;s[j];j++){
			insert(s+j,i);
		}
	}
    cin>>m;
    for(int i=1;i<=m;++i){
        cin>>s;
        int ans = Find(s);
        cout<<ans<<"\n";
    }
    return 0;
}

暴力程式碼

#include<bits/stdc++.h>
using namespace std;
int ans;
char s1[10010][22],s2[22];
int n, m;
int len1[10010],len2;
bool judge(char s1[],char s2[]){
	int j,k;
	int len = strlen(s2);
	int len2 = strlen(s1);
	for(int i=0;i<len;++i){
		for(j=i,k=0;j<len2+i,k<len2;++j){
			//cout<<s1[k]<<" "<<s2[j]<<endl;
			if(s1[k] != s2[j]){break;}
			k++;
		}
		//cout<<j<<endl;
		//cout<<len2<<endl;
		if(k==len2)return true;
	}
	return false;
}
int main(){
	ios::sync_with_stdio(NULL);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i){
		cin>>s1[i];
		len1[i] = strlen(s1[i]);
	}
	cin>>m;
	for(int i=1;i<=m;++i){
		cin>>s2;
		ans = 0;
		int len2=strlen(s2);
		for(int j=1;j<=n;++j){
			if(len2 > len1[j])continue;
			if(judge(s2,s1[j]) == true){
				//cout<<judge(s2,s1[j]);
				ans++;
			}
		}
		cout<<ans<<endl;
	}
	
}

NO.3 SSY的佇列

這個題沒有題解,主要是正解還沒學……

NO.4 清理牛棚

原型可以自行上百度qaq

題目描述

約翰的奶牛們從小嬌生慣養。她們無法容忍牛棚裡的任何髒東西。約翰發現,如果要使這群有潔癖的奶牛滿意,它不得不僱傭她們中的一些來清掃牛棚。

約翰的奶牛中有\(N(1\le N\le 10000)\)頭願意通過清掃牛棚來掙一些零花錢。由於在某個時段中奶牛們會在牛棚裡隨時隨地地亂扔垃圾,自然地,她們要求在這段時間裡,無論什麼時候至少要有一頭奶牛正在打掃。需要打掃的時段從某一天的第M秒開始,到第\(E\)秒結束\((0\le M\le E\le 86399)\).注意這裡的秒是指時間段而不是時間點。也就是說,每天需要打掃的總時間是\(E-M+1\)秒。

約翰已經從每頭牛哪裡得到了她們願意接受的工作計劃:對於每一頭牛,她每天都願意在第\(T1..T2\)秒的時間段內工作\((M\le T1\le T2\le E)\),所要求的報酬是\(S\)美元\((0\le S\le 500000)\)。與需打掃時段的描述一樣,如果一頭奶牛願意工作的時段是每天的第\(10..20\)秒,那她總共工作的時間是\(11\)秒,而不是\(10\)秒。約翰一旦決定僱傭某一頭奶牛,就必須付給她全額的工資,而不能只讓她工作一段時間,然後再按這段時間在她願意工作的總時間中所佔的百分比來決定她的工資。

現在請你幫約翰決定該僱傭那些奶牛以保持牛棚的清潔,當然,在能讓奶牛們滿意的前提下,約翰希望使總花費盡量小。

輸入格式

\(1\)行:\(3\)個正整數\(N,M,E\),用空格隔開。

\(2\)\(N+1\)行:第\(i+1\)行給出了編號為\(i\)的奶牛的工作計劃,即\(3\)個用空格隔開的正整數\(T1,T2,S.\)

輸出格式

輸出一個整數,表示約翰需要為牛棚清理工作支付的最少費用。如果清理工作不可能完成,那麼輸出\(-1\).

樣例

樣例輸入

3 0 4
0 2 3
3 4 2
0 0 1

樣例輸出

5

資料範圍與提示

約翰有\(3\)頭牛。牛棚在第\(0\)秒到第\(4\)秒之間需要打掃。第\(1\)頭牛想要在\(0,1,2\)秒內工作,為此她要求的報酬是\(3\)美元,其餘的依此類推。

分析

此題有許許多多種,在這裡暫時分析一種,因為後邊我沒有理解,但是我會把程式碼放上,只能自行理解了……
最短路
這個演算法比較巧妙,具體思路主要在建邊上,我們把每頭奶牛工作時間之間建邊,然後邊權為報酬,然後在每個時間點之間建邊,邊權為0,這樣就把開始的時間和結束的時間連起來了。當一個點能到達的時候,這個點和起點之間的點一定是全部都被覆蓋了的。具體的一種覆蓋情況見圖解

上邊是有邊權的,下邊反著的邊沒有邊權
這樣就相當於一種覆蓋。只要從題目給的起點到終點全部被覆蓋,那麼跑一遍最短路就行了。如果到達不了,也就是距離為初始化的最大值,那麼輸出\(-1\)。值得注意的是,這個建邊需要的是時間段的建邊,所以建邊的時候要把終點加一而達到建的時間段的邊。又因為正著是有邊權的,逆著的是沒有的,所以就可以達到讓一頭牛可以工作到一半,從另一個時間開始讓另一頭牛工作的目的。(感性理解一下)

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
#define ll long long
struct Node{
    int v,next;
    ll val;
}e[maxn];
int tot;
int head[maxn];
int n,m,E;
void Add(int x,int y,int val){//建邊
    e[++tot].v = y;
    e[tot].next = head[x];
    e[tot].val = val;
    head[x] = tot;
}
ll dis[maxn];
int vis[maxn];
priority_queue<pair<int,int> >q;
void Dij(int m){//迪傑斯特拉求最短路,spfa不穩定
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[m] = 0;
    q.push(make_pair(0,m));
    while(!q.empty()){
        int x = q.top().second;
        q.pop();
        if(vis[x])continue;
        vis[x] = 1;
        for(int i=head[x];i;i=e[i].next){
            int v = e[i].v;
            int z = e[i].val;
            if(dis[v] > dis[x]+z){
                dis[v] = dis[x]+z;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&E);
    for(int i=m;i<E;++i){//每個時間點之間建一個邊權為0的邊
        Add(i+1,i,0);
    }
    for(int i=1;i<=n;++i){
        int x,y,val;
        scanf("%d%d%d",&x,&y,&val);
        if(x<m)x=m;
        if(y>E)y=E;
        Add(x,y+1,val);//每頭牛時間段之間建邊
    }
    Dij(m);
    if(dis[E+1] == 0x3f3f3f3f3f3f3f3f){
        printf("-1\n");
    }
    else {
        printf("%lld",dis[E+1]);//到終點的最短路
    }
    return 0;
}

暴力dp程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;
const int maxm = 9e4+10;
struct Node{
	int l,r,w;
}a[maxn];
int f[maxm];
int n,m,E;
bool cmp(Node a,Node b){
	if(a.r == b.r)return a.l<b.l;
	return a.r<b.r;
}
int ans = 0x3f3f3f3f;
int main(){
	cin>>n>>m>>E;
	for(int i=1;i<=n;++i){
		cin>>a[i].l>>a[i].r>>a[i].w;
	}
	sort(a+1,a+n+1,cmp);
	memset(f,0x3f,sizeof(f));
	for(int i=1;i<=n;++i){
		if(a[i].l<=m)f[i] = a[i].w;
	}
	for(int i=2;i<=n;++i){
		for(int j=i-1;j>0;--j){
			if(a[i].l <= a[j].r+1){
				f[i] = min(f[i],f[j]+a[i].w);
			}
			else break;
		}
		if(a[i].r>=E)ans = min(ans,f[i]);
	}
	if(ans == 0x3f3f3f3f){
		cout<<"-1"<<endl;
	}
	else cout<<ans<<endl;
	return 0;
}	

單調佇列優化dp

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int lqs=1e4+10,INF=0x7f7f7f7f;
struct Node{
	int st,et,fee;
	bool operator <(const Node &A)const{
		if(et==A.et)return st<A.st;
		return et<A.et;
	}
}p[lqs];
int que[lqs],hh,tt;
long long f[lqs];
int main(){
	int n,m,e;
	scanf("%d%d%d",&n,&m,&e);
	e-=m;
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&p[i].st,&p[i].et,&p[i].fee);	
		p[i].st-=m;p[i].et-=m;++p[i].et;
	}
	sort(p+1,p+n+1);
	hh=1,tt=1;
	for(int i=1;i<=n;i++){
		if(hh<=tt&&p[que[tt]].et<p[i].st)continue;
		int l=hh,r=tt;
		while(l<r){
			int mid=l+r>>1;
			if(p[que[mid]].et<p[i].st)l=mid+1;
			else r=mid;
		}
		f[i]=f[que[l]]+p[i].fee;
		//printf("%d %lld %d\n",i,f[i],pos);
		while(hh<=tt&&f[que[tt]]>=f[i])tt--;
		if(p[que[tt]].et<p[i].et)
			que[++tt]=i;
	}
	long long ans=INF;
	while(hh<=tt){
		if(p[que[tt]].et<=e)break;
		ans=min(ans,f[que[tt]]);tt--;
	}
	if(ans==INF)printf("-1\n");
	else printf("%lld\n",ans);
}

相關文章