寒假集训day2

T1:秘密通道
本 题 看 上 去 像 b f s , 但 发 现 假 了 , 因 为 不 知 道 通 道 什 么 时 候 走 本题看上去像bfs,但发现假了,因为不知道通道什么时候走 bfs,
因 为 n < = 500 , 我 们 发 现 可 存 数 组 远 大 于 N ∗ N , 所 以 我 们 可 以 考 虑 对 于 二 元 组 ( x , y ) 之 间 建 边 因为n<=500,我们发现可存数组远大于N*N,所以我们可以考虑对于二元组(x,y)之间建边 n<=500NNxy
对 于 二 元 组 建 边 , 可 以 考 虑 m a p + p a i r , 也 可 以 之 间 将 ( x , y ) h a s h 一 下 , 用 ( m ∗ x + y ) 表 示 h e a d 数 组 , 这 样 就 可 以 用 链 式 前 向 星 存 边 了 , 我 们 发 现 还 有 多 出 来 的 边 , 即 打 开 通 道 节 省 的 时 间 的 边 , 所 以 即 ( x , y ) 连 出 去 有 至 多 8 条 边 , 所 以 总 边 数 至 多 N ∗ N ∗ 8 , 所 以 用 d i j s t r a O ( M l o g N ) 能 过 对于二元组建边,可以考虑map+pair,也可以之间将(x,y)hash一下,用(m*x+y)表示head数组,这样就可以用链式前向星存边了,我们发现还有多出来的边,即打开通道节省的时间的边,所以即(x,y)连出去有至多8条边,所以总边数至多N*N*8,所以用dijstra \color{blue}{O(MlogN)}能过 ,map+pairx,yhashmx+yheadxy8NN8dijstraOMlogN)
注 意 点 : \color{green}{注意点}:

  1. 本题不是正方形,所以ok数组要(1<=y&&y<=n)
  2. hash一定要mx+y,因为m的值有可能大于y,所以nx+y会重,(调了很久)
  3. 本题的边是单向边,因为从一个能到另一个不代表从另一个能到这个
#include<bits/stdc++.h>
using namespace std;

const int N=505;
struct edge{
	int link,vx,vy,lon;
}q[N*N*20];
int head[N*N+N],cnt=0;
int n,m;
char s[N][N];
void put(int x,int y,int xx,int yy,int z){
        q[++cnt].vy=yy;
		q[cnt].vx=xx;
		q[cnt].lon=z;
		q[cnt].link=head[m*x+y];//
		head[m*x+y]=cnt;
}//
bool ok(int x,int y){
	return (1<=x&&x<=n&&1<=y&&y<=m);//
}
int nf[4]={2,3,0,1};
int sx,sy,tx,ty,vis[N][N],up[N][N],dn[N][N],rht[N][N],lft[N][N],lon[N][N],nx[N][N][4],ny[N][N][4];
struct node{
	int val,idx,idy;
	bool operator <(const node&x)const{
		return x.val<val;
	}
};
int diss[N][N];
priority_queue<node> myline; 
bool ex[N][N];
void dijstra(){
	while(!myline.empty()) myline.pop();
	myline.push((node){0,sx,sy});
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		diss[i][j]=N*N*16;
	    ex[i][j]=0;
	}
	diss[sx][sy]=0;
	while(!myline.empty()){
		node uu=myline.top();myline.pop();
		int ux=uu.idx,uy=uu.idy;
		if(ex[ux][uy]) continue;
		ex[ux][uy]=1;
		for(int i=head[ux*m+uy];i;i=q[i].link){
			int vx=q[i].vx,vy=q[i].vy;
			if(ok(vx,vy)){
				if(diss[vx][vy]>diss[ux][uy]+q[i].lon){
					diss[vx][vy]=diss[ux][uy]+q[i].lon;
					myline.push((node){diss[vx][vy],vx,vy});
				}
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",(s[i]+1));
		for(int j=1;j<=m;j++){
			if(s[i][j]=='#') vis[i][j]=1;
			if(s[i][j]=='.') vis[i][j]=0;
			if(s[i][j]=='C') vis[i][j]=2,sx=i,sy=j;
			if(s[i][j]=='F') vis[i][j]=3,tx=i,ty=j;
			lon[i][j]=N*N*16;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(vis[i][j]==1) up[i][j]=i,lft[i][j]=j;
			if(!up[i][j]) up[i][j]=up[i-1][j];
			if(!lft[i][j]) lft[i][j]=lft[i][j-1];
			if(vis[i][j]==0||vis[i][j]==3) {
				if(lft[i][j]){
					nx[i][j][2]=i,ny[i][j][2]=lft[i][j]+1;
					lon[i][j]=min(lon[i][j],j-lft[i][j]);
				}
				if(up[i][j]){
					nx[i][j][1]=up[i][j]+1,ny[i][j][1]=j;
					lon[i][j]=min(lon[i][j],i-up[i][j]);
				}
			}
			if(vis[i][j]==2){//
				if(lft[i][j]){
					nx[i][j][2]=i,ny[i][j][2]=lft[i][j]+1;
					lon[i][j]=min(lon[i][j],j-lft[i][j]);
				}
				if(up[i][j]){
					nx[i][j][1]=up[i][j]+1,ny[i][j][1]=j;
					lon[i][j]=min(lon[i][j],i-up[i][j]);
				}
			}
		}
	}
	for(int i=n;i>=1;i--){
		for(int j=m;j>=1;j--){
			if(vis[i][j]==1) dn[i][j]=i,rht[i][j]=j;
			if(!dn[i][j]) dn[i][j]=dn[i+1][j];
			if(!rht[i][j]) rht[i][j]=rht[i][j+1];
			if(vis[i][j]==0||vis[i][j]==3) {
				if(rht[i][j]){
					nx[i][j][0]=i,ny[i][j][0]=rht[i][j]-1;
					lon[i][j]=min(lon[i][j],rht[i][j]-j);
				}
				if(dn[i][j]){
					nx[i][j][3]=dn[i][j]-1,ny[i][j][3]=j;
					lon[i][j]=min(lon[i][j],dn[i][j]-i);
				}
			}
			if(vis[i][j]==2){
				if(rht[i][j]){
					nx[i][j][0]=i,ny[i][j][0]=rht[i][j]-1;
					lon[i][j]=min(lon[i][j],rht[i][j]-j);
				}
				if(dn[i][j]){
					nx[i][j][3]=dn[i][j]-1,ny[i][j][3]=j;
					lon[i][j]=min(lon[i][j],dn[i][j]-i);
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(vis[i][j]==1) continue;
			if(ok(i+1,j)&&(vis[i+1][j]!=1))put(i,j,i+1,j,1);
			if(ok(i,j+1)&&(vis[i][j+1]!=1))put(i,j,i,j+1,1);
			if(ok(i-1,j)&&(vis[i-1][j]!=1))put(i,j,i-1,j,1);
			if(ok(i,j-1)&&(vis[i][j-1]!=1))put(i,j,i,j-1,1);
		   for(int k=0;k<4;k++){
			   int tmpx=nx[i][j][k],tmpy=ny[i][j][k];
			  if(ok(tmpx,tmpy)&&(vis[tmpx][tmpy]!=1)&&((tmpx!=i)||(tmpy!=j))){
				if(tmpx==i+1&&tmpy==j) continue;
				if(tmpx==i&&tmpy==j+1) continue;
				if(tmpx==i-1&&tmpy==j) continue;
				if(tmpx==i&&tmpy==j-1) continue;
				put(i,j,tmpx,tmpy,lon[i][j]);//
			  }
		   } 
		}
	}
	dijstra();
	if(diss[tx][ty]==N*N*16) {
		puts("nemoguce");
	    return 0;
	}
	printf("%d",diss[tx][ty]);
}

T2:蔡老板与公司
这 道 题 其 实 可 以 当 做 一 类 题 来 做 这道题其实可以当做一类题来做
如 N O I P 2019 d a y 1 T 2 如NOIP2019 day1 T2 NOIP2019day1T2
本 题 记 f [ i ] 表 示 以 i 结 尾 的 最 短 的 合 法 括 号 序 列 , h w [ i ] 表 示 以 在 f [ i ] 条 件 下 合 法 序 列 的 长 度 本题记f[i]表示以i结尾的最短的合法括号序列,hw[i]表示以在f[i]条件下合法序列的长度 f[i]i,hw[i]f[i]
则 f [ i ] = f [ i − h w [ i ] ] + 1 ( 即 以 上 一 个 回 文 点 , 每 一 个 方 案 都 可 以 加 上 本 次 的 方 案 , 同 时 , 也 可 以 不 取 上 一 个 中 的 任 何 一 种 ) 则f[i]=f[i-hw[i]]+1(即以上一个回文点,每一个方案都可以加上本次的方案,同时,也可以不取上一个中的任何一种) f[i]=f[ihw[i]]+1
注 意 点 : \color{blue}注意点:
只 有 h w [ i ] 不 为 0 时 才 可 转 移 \color{blue}只有hw[i]不为0时才可转移 hw[i]0

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e6+5;
char s[N];
int n,hw[N],f[N];
ll ans=0;
int main(){
	scanf("%s",(s+1));
	s[0]='#';
	n=strlen(s+1);
	for(int i=1;i<=n;i++){
		int j=i-1;
		while(hw[j]&&s[j]!=s[i]) j=j-hw[j];
		if(s[j]==s[i]) hw[i]=i-j+1;
		if(hw[i]) f[i]=f[i-hw[i]]+1;
		ans=1ll*(ans+f[i]);
	}
	printf("%lld",ans);
}

T3:小D与子序列

大 概 是 这 种 题 的 套 路 : 大概是这种题的套路:
先 想 , 能 不 能 二 分 , 但 发 现 不 可 做 , 因 为 无 法 判 断 那 些 需 要 加 那 些 不 用 加 先想,能不能二分,但发现不可做,因为无法判断那些需要加那些不用加
观 察 一 下 数 据 发 现 可 以 O ( n 2 ) 观察一下数据发现可以O(n^2) On2
所 以 很 快 就 想 到 了 d p , 由 于 要 为 m , 似 乎 是 背 包 , 所 以 d p [ j ] 表 示 和 为 j 的 最 小 选 的 数 所以很快就想到了dp,由于要为m,似乎是背包,所以dp[j]表示和为j的最小选的数 dpmdp[j]j
d p 数 组 修 改 的 时 候 还 要 顺 便 该 极 差 , 所 以 用 v e c t o r 来 维 护 , 这 样 时 间 复 杂 度 是 O ( n 3 ) , 但 是 实 际 远 远 达 不 到 这 个 上 界 , 因 为 如 果 a [ i ] 很 小 , 栈 的 长 度 就 会 比 较 短 , 反 之 , 合 法 的 d p [ j ] 的 j 就 比 较 大 , 所 以 可 以 过 dp数组修改的时候还要顺便该极差,所以用vector来维护,这样时间复杂度是O(n^3),但是实际远远达不到这个上界,因为如果a[i]很小,栈的长度就会比较短,反之,合法的dp[j]的j就比较大,所以可以过 dp便vectorOn3,a[i]dp[j]j
注 意 点 : \color{brown}注意点: :

  1. vector的使用,还是应该用iterator调用
  2. 当len=0时,front函数会炸,所以要先判断front
#include<bits/stdc++.h>
using namespace std;

const int N=5005;
int n,m,a[N],dp[N],g[N];
vector <int> ss[N];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	sort(a+1,a+n+1);
	for(int i=0;i<=m;i++){
	   dp[i]=N;
	}
	for(int j=0;j<=m;j++){
		ss[j].clear();
	}
	memset(g,0x7f,sizeof(g));
	dp[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			if(j>=a[i]){
				if(dp[j-a[i]]==N) continue;
				if(dp[j]>dp[j-a[i]]+1){
					dp[j]=dp[j-a[i]]+1;
					ss[j].clear();;
					int len=ss[j-a[i]].size();
					int fir;	
					if(len)//
					{
					vector<int>::iterator it;
					fir=ss[j-a[i]].front();
					for(it=ss[j-a[i]].begin();it<ss[j-a[i]].end();it++){
						ss[j].push_back(*it);
					}
				    }
					ss[j].push_back(a[i]);
					if(len) g[j]=a[i]-fir;
					else g[j]=0;
				}
				if(dp[j]==dp[j-a[i]]+1){
					int len=ss[j-a[i]].size();
					if(len)
					{
					int fir=ss[j-a[i]].front(),end=ss[j-a[i]].back();
					if(a[i]-fir<g[j]){//
						g[j]=a[i]-fir;
					    ss[j].clear();
					    vector <int> ::iterator it;
					    for(it=ss[j-a[i]].begin();it<ss[j-a[i]].end();it++)
					    {
					    	ss[j].push_back(*it);
					    }
					    ss[j].push_back(a[i]);
					   }
				    }
				    else{
				    	ss[j].push_back(a[i]);
				    	g[j]=0;
				    }
				}
			}
			else break;
		}
	}
	if(dp[m]==N){
		puts("-1");
		return 0;
	}
	printf("%d",g[m]);
}

改 进 : 改进:
上 述 算 法 的 瓶 颈 在 于 用 v e c t o r , 但 我 们 发 现 栈 尾 的 总 是 a [ i ] , 所 以 其 实 我 们 只 要 记 录 栈 首 就 行 了 上述算法的瓶颈在于用vector,但我们发现栈尾的总是a[i],所以其实我们只要记录栈首就行了 vectora[i],
g [ i ] 表 示 满 足 d p [ i ] 时 的 最 小 极 差 时 的 最 小 值 , f [ i ] 表 示 满 足 d p [ i ] 的 极 差 g[i]表示满足dp[i]时的最小极差时的最小值,f[i]表示满足dp[i]的极差 g[i]dp[i]f[i]dp[i]
则 随 便 转 移 即 可 则随便转移即可 便
注 意 点 : \color{blue}注意点:

  1. 当a[i]时应直接存入数组,这样才能赋初始值
  2. 最好用f数组存答案,不要单独记录m的答案(我也不知道为什么,不这样会炸)
#include<bits/stdc++.h>
using namespace std;

const int N=5005;
int n,m;
int a[N],dp[N],g[N],f[N];
int ans=N,anss=N;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=m;i++){
		dp[i]=N;
		f[i]=N;//
		g[i]=0;
	}
	for(int i=1;i<=n;i++){
		for(int j=m;j>=1;j--){
			if(j-a[i]<=0){break;}
			if(dp[j-a[i]]!=N)
			{	
				 if(dp[j]>dp[j-a[i]]+1)
				 {
			      dp[j]=dp[j-a[i]]+1;
			      g[j]=g[j-a[i]];
			      f[j]=a[i]-g[j];
		         }
		         if(dp[j]==dp[j-a[i]]+1)
		         {
		         	if(a[i]-g[j]>a[i]-g[j-a[i]]){		  
 		         		g[j]=g[j-a[i]];
 		         		f[j]=a[i]-g[j];
		         	}
		         } 
			}
		}
	    dp[a[i]]=1,g[a[i]]=a[i],f[a[i]]=0;//
		if(a[i]==m) {
			ans=0;
			break;
		}
	}
	if(dp[m]==N){
		puts("-1");
		return 0;
	}
	printf("%d",f[m]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值