25-暑期-来追梦noip-卷9 总结
开题顺序:A-D-C-B。
分配时间:A 10min D 10min C 30min B 30min 剩下的时间都是在思考
A
预估 100,实际 41。
二分 + 哈希即可。需要轻微卡常。
实现
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#pragma G++ optimize("Ofast,unroll-loops")
using namespace std;
const int N=2e5+5;
const ull BASE=13331;
int n;
ull H[N],P[N];
string s;
unordered_map<int,bool> mp;
ull get(int l,int r){
    if(l>r)
        return 0;
	return H[r]-H[l-1]*P[r-l+1];
}
bool check(int x){
    mp.clear();
	for(int i=1;i+x-1<=n;i++){
		int h=get(i,i+x-1);
		if(mp[h])
			return 1;
		mp[h]=1;
	}
	return 0;
}
signed main(){
	//freopen("T1.in","r",stdin);
	//freopen("T1.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>s;
	s='#'+s;
	P[0]=1;
	for(int i=1;i<=n;i++)
		H[i]=H[i-1]*BASE+s[i],P[i]=P[i-1]*BASE;
	int l=0,r=n+1;
	while(l+1<r){
		int mid=(l+r)>>1;
		if(check(mid))
			l=mid;
		else
			r=mid;
	}
	cout<<l;
	return 0;
}
B
预估 41,实际 50。
现在我们需要求一个等差数列。
考虑变换一下形式:\(H_i,H_j,H_k \to H_j-d,H_j,H_j+d\),其中 \(d\) 为公差。
然后有一个很妙的操作:对于每个 \(i\),将 \(i\) 左边出现过的数标记为 \(1\),其他为 \(0\)。
这样,如果 \(H_j-d,H_j+d\) 不位于同一侧(即合法),则它们的标记串必定不回文。
然后问题转化为回文判定问题,且带修。于是运用线段树维护哈希值即可。
实现
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;
const int N=3e5+5;
const ull BASE=13331;
int n;
int a[N];
ull tree1[N<<2],tree2[N<<2],P[N];
void pushup1(int p,int lt,int rt){
	int mid=(lt+rt)>>1;
	tree1[p]=tree1[p<<1]*P[rt-mid]+tree1[p<<1|1];
}
void upd1(int p,int lt,int rt,int qx,ull val){
	if(lt>qx||rt<qx)
		return;
	if(lt==rt){
		tree1[p]=val;
		return;
	}
	int mid=(lt+rt)>>1;
	upd1(p<<1,lt,mid,qx,val);
	upd1(p<<1|1,mid+1,rt,qx,val);
	pushup1(p,lt,rt);
}
ull qry1(int p,int lt,int rt,int ql,int qr){
	if(lt>qr||rt<ql)
		return 0;
	if(ql<=lt&&rt<=qr)
		return tree1[p];
	int mid=(lt+rt)>>1;
	if(qr<=mid)
		return qry1(p<<1,lt,mid,ql,qr);
	if(ql>mid)
		return qry1(p<<1|1,mid+1,rt,ql,qr);
	return qry1(p<<1,lt,mid,ql,qr)*P[min(rt-mid,qr-mid)]+qry1(p<<1|1,mid+1,rt,ql,qr);
}
void pushup2(int p,int lt,int rt){
	int mid=(lt+rt)>>1;
	tree2[p]=tree2[p<<1]+tree2[p<<1|1]*P[mid-lt+1];
}
void upd2(int p,int lt,int rt,int qx,ull val){
	if(lt>qx||rt<qx)
		return;
	if(lt==rt){
		tree2[p]=val;
		return;
	}
	int mid=(lt+rt)>>1;
	upd2(p<<1,lt,mid,qx,val);
	upd2(p<<1|1,mid+1,rt,qx,val);
	pushup2(p,lt,rt);
}
ull qry2(int p,int lt,int rt,int ql,int qr){
	if(lt>qr||rt<ql)
		return 0;
	if(ql<=lt&&rt<=qr)
		return tree2[p];
	int mid=(lt+rt)>>1;
	if(qr<=mid)
		return qry2(p<<1,lt,mid,ql,qr);
	if(ql>mid)
		return qry2(p<<1|1,mid+1,rt,ql,qr);
	return qry2(p<<1,lt,mid,ql,qr)+qry2(p<<1|1,mid+1,rt,ql,qr)*P[min(mid-lt+1,mid-ql+1)];
}
signed main(){
	//freopen("T2.in","r",stdin);
	//freopen("T2.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	P[0]=1;
	for(int i=1;i<=n;i++)
		cin>>a[i],P[i]=P[i-1]*BASE;
	for(int i=1;i<=n;i++){
		int len=min(a[i]-1,n-a[i]);
		if(len>=1){
			ull ans1=qry1(1,1,n,a[i]-len,a[i]-1);
			ull ans2=qry2(1,1,n,a[i]+1,a[i]+len);
			//cout<<ans1<<' '<<ans2<<'\n';
			if(ans1!=ans2){
				cout<<"YES";
				return 0;
			}
		}
		upd1(1,1,n,a[i],1);
		upd2(1,1,n,a[i],1);
	}
	cout<<"NO";
	return 0;
}
总结:
- 转化思想的运用(等差数列转化、数组的转化)。
C
预估 0,实际 0。
字典序的比较,因为前缀相同的都要跳过,所以想到字典树维护。
然后对于字典树分叉的地方,只要当前冠军节点向兄弟节点连边跑拓扑即可得到拓扑序,然后就能得到字典序。
这里需要注意一个点,就是前缀相同的不能算。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,M=48;
int n,tot;
string s[N];
int trie[N][M],cnt[N],in[M];
vector<char> ans;
vector<int> G[M];
void insert(string x){
	int cur=0;
	for(int i=0;i<x.size();i++){
		if(!trie[cur][x[i]-'a'])
			trie[cur][x[i]-'a']=++tot;
		cur=trie[cur][x[i]-'a'];
	}
	cnt[cur]=1;
}
int search(string x){
	int cur=0,res=0;
	for(int i=0;i<x.size()-1;i++){
		cur=trie[cur][x[i]-'a'];
		res+=cnt[cur];
	}
	return res;
}
bool topo(){
	queue<int> q; 
	for(int i=0;i<26;i++)
		if(!in[i])
			q.push(i);
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		ans.push_back((char)(cur+'a'));
		for(int i:G[cur]){
			in[i]--;
			if(!in[i])
				q.push(i);
		}
	}
	for(int i=0;i<26;i++)
		if(in[i]>0)
			return 0;
	return 1;
}
bool check(string x){
	for(int i=0;i<26;i++)
		G[i].clear();
	for(int i=0;i<26;i++)
		in[i]=0;
	int cur=0;	
	for(int i=0;i<x.size();i++){
		for(int j=0;j<26;j++)
			if(trie[cur][j]&&j!=(x[i]-'a'))
				G[x[i]-'a'].push_back(j),in[j]++;
		cur=trie[cur][x[i]-'a'];
	}
	return topo();
}
signed main(){
	//freopen("T3.in","r",stdin);
	//freopen("T3.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>s[i],insert(s[i]);
	for(int i=1;i<=n;i++){
		if(!search(s[i])&&check(s[i])){
			for(char i:ans)
				cout<<i;
			cout<<'\n';
			ans.clear();
		}
		else{
			cout<<"nemoguce\n";
			ans.clear();
		}
	}
	return 0;
}
总结:
- 字典序想到字典树、拓扑排序。
D
预估 0,实际 0。
令 \(dp_{i,j}\) 表示前 \(i\) 个氨基酸,且匹配到碱基串的第 \(j\) 位的方案数。
初始 \(dp_{0,0}=1\),答案 \(\sum dp_{k,i}\)。
转移 \(dp_{i,j}=dp_{i,j}+dp_{i-1,j-len}\),其中 \(len\) 是第 \(k\) 个氨基酸选择的碱基序列。
此处转移因为要和 \(s\) 匹配,所以可以在 kmp 匹配上的时候转移。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int K=1e2+5,A=11,S=1e4+5,MOD=1e9+7;
int n,k,ans;
int dp[K][S],a[K];
string s,t[K][A];
int nxt[S];
void getnxt(string t){
	int i=0,j=-1;
	nxt[0]=-1;
	for(;i<t.size();){
		if(j==-1||t[i]==t[j])
			i++,j++,nxt[i]=j;
		else
			j=nxt[j];
	}
}
void kmp(string s,string t,int cur){
	getnxt(t);
	int i=0,j=0;
	for(;i<s.size();){
		if(j==t.size()-1&&s[i]==t[j]){
			if(i>=t.size())
				dp[cur][i]=(dp[cur][i]+dp[cur-1][i-t.size()])%MOD;
			j=nxt[j];
		}
		if(j==-1||s[i]==t[j])
			i++,j++;
		else
			j=nxt[j];
	}
}
signed main(){
	cin>>k>>s,n=s.size(),s="#"+s;
	for(int i=1;i<=k;i++){
		cin>>a[i];
		for(int j=1;j<=a[i];j++)
			cin>>t[i][j];
	}
	for(int i=0;i<n;i++) dp[0][i]=1;
	for(int i=1;i<=k;i++){
		for(int u=1;u<=a[i];u++){
			kmp(s,t[i][u],i);
		}
	}
	for(int i=1;i<=n;i++) ans=(ans+dp[k][i])%MOD;
	cout<<ans;
	return 0;
}
结语
成绩:41+50+0+0=91。
问题:对于转化思想和字典序问题不熟悉,dp 很弱。
方案:多练习 dp,其他同上总结。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号