【学习笔记】NOIP模拟赛

bot 的能量堆(energy)

假设当前数为 n n n,两个数之差为 d d d。考虑除以一个质数 p p p后, ( n , d ) → ( ⌊ n p ⌋ , d p ) , ( ⌈ n p ⌉ , d p ) (n,d)\to (\lfloor\frac{n}{p}\rfloor,\frac{d}{p}),(\lceil\frac{n}{p}\rceil,\frac{d}{p}) (n,d)(⌊pn,pd),(⌈pn,pd),也就是说有向上和向下取整两种情况。注意到两种情况只会往加减次数少的那一边走,可以近似认为只有一种情况。

根据整除分块的基本知识我们知道合法的状态数只有 d ( n ) d(n) d(n)级别(假设每次都是向下取整),因此直接记忆化搜索即可。

n = m n=m n=m的情况很傻逼。也就只能暴搜了。复杂度 O ( 玄学 ) O(玄学) O(玄学)

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
using namespace std;
int T,n,m;
int pr[105],cnt,now;
map<pair<int,int>,int>id;
void dfs(int m,int n,int stp){
	if(stp>=now||id.find(make_pair(m,n))!=id.end()&&stp>=id[make_pair(m,n)])return;now=min(now,stp+n-1);
	id[make_pair(m,n)]=stp;
	for(int i=1;i<=cnt;i++){
		if(m%pr[i]==0){
			int k=n%pr[i];
			if(n<pr[i]){
				now=min(now,stp+pr[i]-n+1);
			}
			else {
				if(k<=pr[i]-k){
					dfs(m/pr[i],(n-k)/pr[i],stp+k+1);
				}
				if(pr[i]-k<=k){
					dfs(m/pr[i],(n-k)/pr[i]+1,stp+pr[i]-k+1);
				}
			}
		}
	}
}
int main(){
    freopen("energy.in","r",stdin);
    freopen("energy.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		if(n==m){
			cout<<1<<"\n";
		}
		else {
			if(n>m)swap(n,m);m-=n,id.clear();
			cnt=0;int m2=m;
			for(int i=2;i<=m2/i;i++){
				if(m2%i==0){
					pr[++cnt]=i;while(m2%i==0)m2/=i;
				}
			}if(m2>1)pr[++cnt]=m2;
			now=0x3f3f3f3f,dfs(m,n,0);
			cout<<now<<"\n";
		}
	}
} 

bot 的矩阵(matrix)

考场上想复杂了。我是sb

考虑将行和列都看成一个点,建立二分图,如果 ( x , y ) (x,y) (x,y)没有确定那么从左部点 x x x向右部点 y y y连一条边,我们的任务是对每条边确定一个权值,使得左右节点的点权等于与之相连的边权和。

对于一个连通块,其有解的必要条件是左部点 ∑ a i \sum a_i ai=右部点 ∑ b i \sum b_i bi

我们考虑抽取其中一颗生成树,然后自底向上为边赋值,不在生成树上的边权设为 0 0 0

对于已经确定的边,只需让对应的点权减去这个值即可。

复杂度 O ( n 2 ) O(n^2) O(n2)

考场上想了很蠢的构造。我也是醉了。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
using namespace std;
int T,n,m;
ll a[4005],c[2005*2005],sum;
int v[2005*2005],vs[4005];
vector<int>g[4005];
inline ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
	return x*f;
}
int has(int x,int y){
	return (x-1)*n+y;
}
void dfs(int u){
	vs[u]=1;
	for(auto v:g[u]){
		if(!vs[v]){
			dfs(v);
			if(u>n)c[has(v,u-n)]=a[v];
			else c[has(u,v-n)]=a[v];
			a[u]-=a[v];
		}
	}
}
void solve(){
	for(int i=1;i<=2*n;i++){
		if(!vs[i]){
			dfs(i);
			if(a[i]!=0){
				cout<<"NoSolution!"<<"\n";
				return;
			}
		}
	}cout<<"Botany!"<<"\n";
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cout<<c[has(i,j)]<<' ';
		}cout<<"\n";
	}
}
int main(){
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	T=read();
	while(T--){
		n=read(),m=read();
		for(int i=1;i<=2*n;i++)a[i]=read(),vs[i]=0,g[i].clear();for(int i=1;i<=n*n;i++)v[i]=c[i]=0;
		for(int i=1;i<=m;i++){
			int x=read(),y=read();ll z=read();
			a[x]-=z,a[n+y]-=z,c[has(x,y)]=z,v[has(x,y)]=1;
		}for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(!v[has(i,j)])g[i].pb(j+n),g[j+n].pb(i);
			}
		}solve();
		
	}
} 

bot 的殖民扩张(expansion)

好题。

首先考虑一条链的情况, a 1 = 1 , a n = m a_1=1,a_n=m a1=1,an=m

不妨将问题一般化,每个节点都有覆盖半径 d i d_i di,问你能否把 [ 1 , m ] [1,m] [1,m]覆盖完

考虑设 d p i dp_i dpi表示对前 i i i个点定向,能覆盖的最远距离。考虑如下转移:

1.1 1.1 1.1 i i i向右边定向, d p i ← max ⁡ ( d p i − 1 , a i + d ) [ d p i − 1 ≥ a i − 1 ] dp_i\gets \max(dp_{i-1},a_i+d)[dp_{i-1}\ge a_i-1] dpimax(dpi1,ai+d)[dpi1ai1]
1.2 1.2 1.2 i i i向左边定向,与一段前缀 j j j共同覆盖了 [ 1 , a i ] [1,a_i] [1,ai],此时对于 j < k < i j<k<i j<k<i的点都往右边定向,不妨取一段最小的前缀 j j j d p i ← max ⁡ ( a i , max ⁡ j < k < i ( a k + d k ) ) dp_{i}\gets \max(a_i,\max_{j<k<i}(a_k+d_k)) dpimax(ai,maxj<k<i(ak+dk))
1.3 1.3 1.3 i i i直接继承前 i − 1 i-1 i1的最远覆盖距离, d p j ← d p j − 1 dp_{j}\gets dp_{j-1} dpjdpj1

本题的 d i d_i di是定值,那么首先判断 d p i − 1 dp_{i-1} dpi1能否覆盖到 i − 1 i-1 i1,否则判断 d p i − 2 dp_{i-2} dpi2能否覆盖到 a i − d − 1 a_i-d-1 aid1(这种情况下的最远距离是 max ⁡ ( a i , a i − 1 + d ) \max(a_i,a_{i-1}+d) max(ai,ai1+d)),否则判断 d p i − 1 dp_{i-1} dpi1能否覆盖到 a i − d − 1 a_i-d-1 aid1,否则无解(因为后面一段和前面一段无论如何都接不到一起)

对于环上的情况,考虑从环上距离最大的两个点处断开,记作 L L L。如果 L ≤ d L\le d Ld显然有解,否则若 a 1 = 1 a_1=1 a1=1往左倒,只需求出 a 2 ∼ n a_{2\sim n} a2n能向右扩展的最远距离并判断是否 d p n − a n + d ≥ L dp_n-a_{n}+d\ge L dpnan+dL即可。若 a 1 = 1 a_1=1 a1=1往右倒,那么 a 2 a_2 a2一定往左倒,判断 d − a 2 + 1 + d p n − a n ≥ L d-a_2+1+dp_n-a_n\ge L da2+1+dpnanL即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

tips:事实上是对两个基本模型的拼接与考察,但是考场上似乎没有想到破环那一步??

我信仰什么,我便实现哪种方法

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define int ll
using namespace std;
int m,n,L,a[1000005],s[1000005],b[1000005],dp[1000005];
bool check(int d){
	dp[0]=1;
	for(int i=1;i<n;i++){
		if(dp[i-1]>=b[i]-1)dp[i]=b[i]+d;
		else if(i>=2&&dp[i-2]>=b[i]-d-1)dp[i]=max(b[i],b[i-1]+d);
		else if(dp[i-1]>=b[i]-d-1)dp[i]=b[i];
		else return 0;
	}
	return dp[n-1]-b[n-1]+d>=L;
}
bool check2(int d){
	if(b[1]-d>=1)return 0;
	dp[0]=dp[1]=d+1;
	for(int i=2;i<n;i++){
		if(dp[i-1]>=b[i]-1)dp[i]=b[i]+d;
		else if(i!=2&&dp[i-2]>=b[i]-d-1)dp[i]=max(b[i],b[i-1]+d);
		else if(dp[i-1]>=b[i]-d-1)dp[i]=b[i];
		else return 0;
	}return d-(b[1]-1)+dp[n-1]-b[n-1]>=L;
}
signed main() {
	freopen("expansion.in","r",stdin);
	freopen("expansion.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>m>>n;
	if(n==1){
		cout<<m-1;
		return 0;
	}
	for(int i=0;i<n;i++)cin>>a[i];
	sort(a,a+n);s[0]=a[0]+m-a[n-1]-1;int mx=0;
	for(int i=1;i<n;i++){
		s[i]=a[i]-a[i-1]-1;
		if(s[i]>s[mx])mx=i;
	}b[0]=1;
	for(int i=1;i<n;i++){
		b[i]=b[i-1]+s[(mx+i)%n]+1;
	}L=s[mx];
	int l=0,r=L,res=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)||check2(mid))res=mid,r=mid-1;
		else l=mid+1;
	}cout<<res;
}

bot 的字符串(palindrome)

想复杂了。

f l , r , i f_{l,r,i} fl,r,i表示处理完 [ l , r ] [l,r] [l,r] r r r必选)右边多了 i ( i ≥ 0 ) i(i\ge 0) i(i0)个的方案数, g l , r , i g_{l,r,i} gl,r,i表示处理完 [ l , r ] [l,r] [l,r] l l l必选)左边多了 i ( i > 0 ) i(i>0) i(i>0)个的方案数。答案是 ∑ i = 1 n f 1 , i , 0 \sum_{i=1}^nf_{1,i,0} i=1nf1,i,0

简单分析一波。 T T T从中心扩展时只有唯一一种生成方式,也就对应唯一的转移,因此每种方案恰好只会被计算一次。注意到总状态数 O ( n ∑ s i ) O(n\sum{s_i}) O(nsi),而转移复杂度 O ( 1 ) O(1) O(1),可以通过。

总复杂度 O ( n ∑ s i ) O(n\sum s_i) O(nsi)

实现比较复杂。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define ull unsigned long long
using namespace std;
const int mod=1e9+7;
int n;
string s[505];
vector<int>f[505][505],g[505][505];
ull fac[200005];
vector<ull>pre[505],suf[505];
ull gp(int x,int l,int r){
	if(l==0)return pre[x][r];
	return pre[x][r]-pre[x][l-1]*fac[r-l+1];
}
ull gs(int x,int l,int r){
	return suf[x][l]-suf[x][r+1]*fac[r-l+1];
}
void add(int &x,int y){
	if((x+=y)>=mod)x-=mod;
}
int main() {
    freopen("palindrome.in","r",stdin);
    freopen("palindrome.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;for(int i=1;i<=n;i++)cin>>s[i];
    fac[0]=1;for(int i=1;i<=200000;i++)fac[i]=fac[i-1]*1331;
	for(int i=1;i<=n;i++){
		pre[i].resize(s[i].size()+5);
    	suf[i].resize(s[i].size()+5);
    	for(int j=i;j<=n;j++){
    		f[i][j].resize(s[j].size()+5);
    		g[i][j].resize(s[i].size()+5);
		}
	}
	for(int i=1;i<=n;i++){
		pre[i][0]=s[i][0];
		for(int j=1;j<s[i].size();j++){
			pre[i][j]=pre[i][j-1]*1331+s[i][j];
		}
		for(int j=s[i].size()-1;j>=0;j--){
			suf[i][j]=suf[i][j+1]*1331+s[i][j];
		}
		for(int j=0;j<s[i].size();j++){
			if(gp(i,0,s[i].size()-j-1)==gs(i,0,s[i].size()-j-1)){
				f[i][i][j]=1;
			}
		}
		for(int j=1;j<s[i].size();j++){
			if(gp(i,j,s[i].size()-1)==gs(i,j,s[i].size()-1)){
				g[i][i][j]=1;
			}
		}
	}
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n-len+1;l++){
			int r=l+len-1;
			if(s[l].size()<=s[r].size()&&gs(l,0,s[l].size()-1)==gp(r,0,s[l].size()-1)){
				add(f[l][r][s[r].size()-s[l].size()],1);
			}
			if(s[l].size()>s[r].size()&&gs(l,s[l].size()-s[r].size(),s[l].size()-1)==gp(r,0,s[r].size()-1)){
				add(g[l][r][s[l].size()-s[r].size()],1);
			}
			for(int i=0;i<=s[r].size();i++){
				add(f[l][r][i],f[l+1][r][i]);
				if(i<s[l].size()&&gs(l,s[l].size()-i,s[l].size()-1)==gp(r,s[r].size()-i,s[r].size()-1)){
					add(g[l][r][s[l].size()-i],f[l+1][r][i]);
				}
				if(i>=s[l].size()&&gs(l,0,s[l].size()-1)==gp(r,s[r].size()-i,s[r].size()-i+s[l].size()-1)){
					add(f[l][r][i-s[l].size()],f[l+1][r][i]);
				}
			}
			for(int i=1;i<=s[l].size();i++){
				add(g[l][r][i],g[l][r-1][i]);
				if(i<=s[r].size()&&gs(l,0,i-1)==gp(r,0,i-1)){
					add(f[l][r][s[r].size()-i],g[l][r-1][i]);
				}
				if(i>s[r].size()&&gs(l,i-s[r].size(),i-1)==gp(r,0,s[r].size()-1)){
					add(g[l][r][i-s[r].size()],g[l][r-1][i]);
				}
			}
		}
	}
	int res=0;
	for(int i=1;i<=n;i++){
		add(res,f[1][i][0]);
	}cout<<res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值