noip模拟35[第一次4题·裂了]

noip模拟35 solutions

这是我第一次这么正式的考四个题,因为这四个题都出自同一个出题人,并不是拼盘拼出来的。

但是考得非常的不好,因为题非常难而且一直想睡觉。。

有好多我根本就不会的算法,比如说笛卡尔树,时至今日我还是不会

对我来说好像并不是一个很大的提升,因为好多题我不能只看着题解改出来

总是要问别人。。。。。我好菜啊好菜啊好菜啊

T1 玩游戏

其实这个题我在考场上的时候已经想到了正解的一半,

一眼看过去这就是个贪心,我就在草稿纸上手推贪心策略,推了半天发现到了最小值就走不动了

没有发现还可以从左右两端向中间贪心,于是这个题就做完了

我们从k位置将整个序列分成两个块,分别对两个块统计前缀和

每次寻找下一个比当前值小的值并向这个位置贪心,一直贪心到最小值

再从这个前缀和数组的最后往前贪心,一样贪到最小值

如果都能到达最小值,那么就合法,反之不合法。。

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1e5+5;
int T,n,k;
ll a[N],b[N],cb,c[N],cc;
ll prb[N],prc[N];
int nxb[N],nxc[N];
signed main(){
	scanf("%d",&T);
	while(T--){
		/*memset(prb,0,sizeof(prb));
		memset(prc,0,sizeof(prc));*/
		memset(nxb,0,sizeof(nxb));
		memset(nxc,0,sizeof(nxc));
		scanf("%d%d",&n,&k);
		for(re i=1;i<=n;i++)scanf("%lld",&a[i]);
		c[cc=1]=0;for(re i=k+1;i<=n;i++)c[++cc]=a[i];
		b[cb=1]=0;for(re i=k;i>1;i--)b[++cb]=a[i];
		for(re i=1;i<=cb;i++)prb[i]=prb[i-1]+b[i];
		for(re i=1;i<=cc;i++)prc[i]=prc[i-1]+c[i];
		ll mn=prb[1],who=1,mb,mc;;
		for(re i=2;i<=cb;i++)if(prb[i]<=mn){nxb[who]=i;who=i;mn=prb[i];}
		mn=prb[cb];who=cb;
		for(re i=cb-1;i>=1;i--)if(prb[i]<mn){nxb[who]=i;who=i;mn=prb[i];}
		mn=prc[1];who=1;
		for(re i=2;i<=cc;i++)if(prc[i]<=mn){nxc[who]=i;who=i;mn=prc[i];}
		mn=prc[cc];who=cc;
		for(re i=cc-1;i>=1;i--)if(prc[i]<mn){nxc[who]=i;who=i;mn=prc[i];}
		//for(re i=1;i<=cb;i++)cout<<prb[i]<<" ";cout<<endl;
		//for(re i=1;i<=cc;i++)cout<<prc[i]<<" ";cout<<endl;
		int x=1,y=1;
		bool flag=false;
		if(prb[cb]+prc[cc]>0){
			printf("No\n");continue;
		}
		while(nxb[x]||nxc[y]){
			//cout<<x<<" "<<y<<endl;
			flag=false;
			for(re i=x+1;i<=nxb[x];i++)
				if(prb[i]+prc[y]>0){
					flag=true;
					break;
				}
			if(!nxb[x])flag=true;
			if(flag){
				flag=false;
				for(re i=y+1;i<=nxc[y];i++)
					if(prb[x]+prc[i]>0){
						flag=true;
					}
				if(!nxc[y])flag=true;
				if(flag)break;
				else y=nxc[y];
				flag=false;continue;
			}
			x=nxb[x];flag=false;
		}
		if(flag){
			printf("No\n");continue;
		}
		x=cb;y=cc;
		while(nxb[x]||nxc[y]){
			//if(x!=0)cout<<x<<" "<<nxb[x]<<" "<<y<<" "<<nxc[y]<<endl;
			flag=false;
			if(nxb[x]){
				//cout<<nxb[x]<<endl;
				for(re i=x-1;i>=nxb[x];i--)
					if(prb[i]+prc[y]>0){
						flag=true;
						break;
					}
			}
			else flag=true;
			//cout<<flag<<endl;
			if(flag){
				flag=false;
				if(nxc[y]){
					for(re i=y-1;i>=nxc[y];i--)
						if(prb[x]+prc[i]>0){
							flag=true;
						}
				}
				else flag=true;
				//cout<<flag<<endl;
				if(flag)break;
				else y=nxc[y];
				flag=false;continue;
			}
			x=nxb[x];flag=false;
		}
		if(flag){
			printf("No\n");continue;
		}
		printf("Yes\n");
	}
}

T2 排列

这个我是真的不知道怎么做,所以直接弃掉了。

所以说我已经发现了一个规律,对于这种随机搞出来的方案题

一般都是直接dp,而且是啥都不在乎的dp,就暴力转移就完事了

其实对于这个题我们完全可以直接全排列做的。。。。。。。爬

设dp[i][j][0/1][0/1]表示前i个点,消了j次能够只剩1个的方案数

你发现我这个东西转移的时候只能去分别枚举左右区间需要的次数,

这样的话,你就得有四层循环,一层枚举i,一层枚举j,一层枚举当前层的最大值位置

还有一层枚举两个子区间的j,

伪代码
for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
		for(int k=1;k<=n;k++)
			for(int jj=1;jj<j;j++){
				dp[i][j][0/1][0/1]=dp[k-1][j-1][0/1][0/1]*dp[i-k][jj][0/1][0/1];
				dp[i][j][0/1][0/1]=dp[k-1][jj][0/1][0/1]*dp[i-k][j-1][0/1][0/1];
			}
此处省略许多细节。。。。。看AC_code

这样的话,你不T谁T,TTTTTTTTTTTTTTTTTTTTTTTTT飞了好吧

这样你发现这个转移好像并不是那么严谨,许多的转移都是同一个形式,

前缀和优化就是最好的选择,毕竟方程都是差不多的,

那么前缀和优化之后的方程就表示至多消j步的方案数,转移仍然是一样的

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=1005;
ll n,m,mod;
ll dp[N][N][2][2];
ll c[N][N];
signed main(){
	scanf("%lld%lld%lld",&n,&m,&mod);
	for(re i=0;i<=m;i++)dp[0][i][0][0]=dp[0][i][0][1]=dp[0][i][1][0]=dp[0][i][1][1]=1;
	c[0][0]=1;c[1][0]=1;c[1][1]=1;
	for(re i=2;i<=n;i++){
		c[i][0]=1;c[i][i]=1;
		for(re j=1;j<i;j++){
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
			//cout<<c[i][j]<<" ";
		}
		//cout<<endl;
	}
	for(re i=1;i<=n;i++){
		for(re j=1;j<=m;j++){
			for(re k=1;k<=i;k++){
				dp[i][j][0][0]=(dp[i][j][0][0]+dp[k-1][j][0][1]*dp[i-k][j][1][0]%mod*c[i-1][k-1])%mod;
				dp[i][j][0][1]=(dp[i][j][0][1]+dp[k-1][j][0][1]*dp[i-k][j-1][1][1]%mod*c[i-1][k-1])%mod;
				dp[i][j][1][0]=(dp[i][j][1][0]+dp[k-1][j-1][1][1]*dp[i-k][j][1][0]%mod*c[i-1][k-1])%mod;
				dp[i][j][1][1]=(dp[i][j][1][1]+(dp[k-1][j][1][1]*dp[i-k][j][1][1]-(dp[i-k][j][1][1]-dp[i-k][j-1][1][1])*(dp[k-1][j][1][1]-dp[k-1][j-1][1][1])%mod)%mod*c[i-1][k-1])%mod;
			}
		}
	}
	ll ans=(dp[n][m][0][0]+mod-dp[n][m-1][0][0])%mod;
	printf("%lld",ans);
}

T3 最短路

这个题我好像是用yubai的乱搞做法过去的,好像也是正解,反正跑的还挺快

简单来说就是一个分层图,也不能说是分层,就是两个点一起走

然后对于重复的点来说就直接用一个bitset维护就好了,其实bool数组也是可以的

好像正确性并不能保证,对于当前的两个点来说后面可能会经过一个点

使得当前的点的较大值能够得到最优解,所以这个题我就弃了

放上我的假代码。。。。

在隔壁Varuxn的帮助下,我成功的找到了正确性

因为我的dij中每一步都是单一的一个点向前跳,这样的话我就保证了一个点不动,另外一个点走完全程

这样我就可以遍历到所有的状态

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=255;
const int M=255*255;
int n,m,val[N];
struct EDGE{
	int to[M],nxt[M],head[N],rp;
	EDGE(){}
	void add_edg(int x,int y){
		to[++rp]=y;
		nxt[rp]=head[x];
		head[x]=rp;
	}
}be,af;
int ans[N][N];
bitset<N> bit[N][N];
struct node{
	int x,y,dis;
	node(){}
	node(int a,int b,int c){
		x=a;y=b;dis=c;
	}
	bool operator < (node a)const{
		return dis>a.dis;
	}
};
priority_queue<node> q;
bool vis[N][N];
void dij(){
	bit[1][1].set(1);
	ans[1][1]=val[1];
	q.push(node(1,1,val[1]));
	while(!q.empty()){
		int x=q.top().x;
		int y=q.top().y;
		int dis=q.top().dis;q.pop();
		if(vis[x][y])continue;
		vis[x][y]=true;
		//cout<<x<<" "<<y<<" "<<dis<<endl;
		for(re i=be.head[x];i;i=be.nxt[i]){
			int tx=be.to[i];
			int tmp=dis;
			if(bit[x][y][tx]==0)tmp+=val[tx];
			if(tmp>=ans[tx][y])continue;
			bit[tx][y]=bit[x][y];
			bit[tx][y].set(tx);
			ans[tx][y]=tmp;
			q.push(node(tx,y,tmp));
		}
		for(re i=af.head[y];i;i=af.nxt[i]){
			int ty=af.to[i];
			int tmp=dis;
			if(bit[x][y][ty]==0)tmp+=val[ty];
			if(tmp>=ans[x][ty])continue;
			bit[x][ty]=bit[x][y];
			bit[x][ty].set(ty);
			ans[x][ty]=tmp;
			q.push(node(x,ty,tmp));
		}
	}
}
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)scanf("%d",&val[i]);
	for(re i=1;i<=m;i++){
		int x,y;scanf("%d%d",&x,&y);
		be.add_edg(x,y);
		af.add_edg(y,x);
	}
	memset(ans,0x3f,sizeof(ans));
	dij();//cout<<ans[n][n]<<endl;
	if(ans[n][n]==0x3f3f3f3f)printf("-1");
	else printf("%d",ans[n][n]);
}

T4 矩形

我走了,我走了,我不会,我写不出来,我根本不会打。。。。。

我调完了,为啥呢,我也不知道为啥,反正我的方法好像有那么亿点点问题

我们对于每一个矩形排序,使得x小的在前面

一般这种矩形的题,我们就直接上扫描线就行了,再加个线段树,

因为我们是按照x从小到大排序的,所以先扫描到的一定在前面

我们直接利用下边界来加并查集,用上边界来更新扫描线,就完事了

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,maxn=1e5,ans;
struct matrix{
	int x1,y1,x2,y2;
	matrix(){}
	bool operator < (matrix x)const{
		if(x1 != x.x1)return x1 < x.x1;
		return x2 < x.x2;
	}
}sca[N];
struct BCJ{
	int fa[N];
	BCJ(){for(re i = 1;i <= 1e5;i ++)fa[i]=i;}
	int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
	void ins(int x,int y){if(find(x)!=find(y))fa[find(x)]=find(y);}
}bcj;
struct seg_tree{
	#define ls x<<1
	#define rs x<<1|1
	int kin[N*4],now[N*4],laz[N*4],up[N*4];
	seg_tree(){
		for(re i = 1;i <= 400000;i ++)kin[i] = 1;
	}
	void pushup(int x){
		if(kin[ls]==2||kin[rs]==2)kin[x]=2;
		else if(now[ls] == now[rs])now[x] = now[ls],up[x] = up[ls],kin[x] = 1;
		else kin[x] = 2;
		return ;
	}
	void pushdown(int x){
		//cout<<x<<" "<<endl;
		if(!laz[x])return ;
		laz[ls] = laz[rs] = laz[x];
		now[ls] = now[rs] = now[x];
		up[ls] = up[rs] = up[x];
		laz[x]=0;
		return ;
	}
	void ins(int x,int l,int r,int ql,int qr,int upp,int id){
		//cout<<x<<" "<<l<<" "<<r<<endl;
		if(ql <= l && r <= qr && kin[x] == 1){
			if(up[x]<upp)up[x] = upp,now[x] = id,laz[x] = 1;
			return ;
		}
		//cout<<"ins "<<x<<" "<<l<<" "<<r<<" "<<ql<<" "<<qr<<endl;
		pushdown(x);
		int mid = l + r >> 1;
		if(ql <= mid)ins(ls,l,mid,ql,qr,upp,id);
		if(qr > mid)ins(rs,mid + 1,r,ql,qr,upp,id);
		pushup(x);return ;
	}
	void query(int x,int l,int r,int ql,int qr,int upp,int id){
		if(ql <= l && r <= qr && kin[x] == 1){
			if(up[x]>=upp)bcj.ins(id,now[x]);
			return ;
		}
		//cout<<"query "<<x<<" "<<l<<" "<<r<<" "<<ql<<" "<<qr<<endl;
		pushdown(x);
		int mid = l + r >> 1;
		if(ql <= mid) query( ls, l ,mid,ql,qr,upp,id);
		if(qr>mid)query(rs,mid+1,r,ql,qr,upp,id);
		pushup(x);return ;
	}
	#undef ls
	#undef rs
}xds;
signed main(){
	scanf("%d", &n );
	for(re i = 1;i <= n;i ++)scanf("%d%d%d%d",&sca[i].x1,&sca[i].y1,&sca[i].x2,&sca[i].y2);
	sort(sca + 1,sca + n + 1);
	for(re i = 1;i <= n;i ++){
		xds.query(1,1,maxn,sca[i].y1,sca[i].y2,sca[i].x1,i);
		xds.ins(1,1,maxn,sca[i].y1,sca[i].y2,sca[i].x2,i);
	}
	for(re i = 1;i <= n;i ++){
		if(bcj.fa[i] == i)ans ++;//cout << i << endl;
	}
	printf("%d",ans);
}
posted @ 2021-08-12 20:27  fengwu2005  阅读(105)  评论(0编辑  收藏  举报