20211111 NOIP模拟赛

前言

好累呀!累死了!好不容易改完三道题!蒟蒻咆哮!!
日常挂分.jpg(数组开大爆了
垫底次数+=1;

正题

T1:(简 单 图 论
得分:\(\color{red}{10}\)

刚了一个小时放弃,然后乱写一个搜索。

正解:

将边按照d值的大小排序,从小到大依次枚举解锁当前边的时间情况。
使用三个矩阵:

\(a[i][j]表示i与j之间有无一条路径\)
\(b[i][j]表示当前能否从i点走到j点\)
\(f[i][j]表示当前从i到j的路径长度\)

其中a,b可以用bitset代替。f每一次用Floyd更新。

当枚举到一条边时,距离解锁该边还要走\(\Delta d\)步,我们就考虑在这几步中,他可以到达那些节点。以此更新b。

每走一步,a就要乘上自己一次,更新路径数量,再把a的幂与b相乘,这样更新b矩阵,注意这里a不会变,我们只使用它的值来乘方,乘方后的矩阵就是所有的走\(\Delta d\)的情况。

然后使用Floyd更新新加入的边对于路径长度的影响。

最后记录当前的\(d\),再更新a。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
struct node{
	int u,v,d;
	bool operator < (const node &b){
		return d<b.d;
	}
}p[160];
const int N=160;
bitset<N> a[N],b[N];
int f[N][N];
void Mul(bitset<N> *a,bitset<N> *b){
	bitset<N> ret[N];
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]){
				ret[i]|=b[j];
			}
		}
	}
	for(int i=1;i<=n;i++){
		a[i]=ret[i];
	}
	return ;
}

void Pow(bitset<N> *a,int b){
	bitset<N> ret[N];
	for(int i=1;i<=n;i++){
		ret[i][i]=1;
	}
	while(b!=0){
		if(b&1){
			Mul(ret,a);
		}
		Mul(a,a);
		b>>=1;
	}
	for(int i=1;i<=n;i++){
		a[i]=ret[i];
	}
	return ;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>p[i].u>>p[i].v>>p[i].d;
	}	
	sort(p+1,p+m+1);
	memset(f,inf,sizeof f);
	for(int i=1;i<=n;i++){//路径长度
		f[i][i]=0;
	}
	int cnt=0;
	int ans=inf;
	for(int i=1;i<=n;i++){
		b[i][i]=1;//自己可以到达自己
	}
	for(int i=1;i<=m;i++){
		int x=p[i].u,y=p[i].v,d=p[i].d;
		int delta=d-cnt;//$\Delta d$
		bitset<N> tmp[N];
		for(int i=1;i<=n;i++){
			tmp[i]=a[i];
		}
		Pow(tmp,delta);//快速幂
		Mul(b,tmp);
		for(int j=1;j<=n;j++){
			for(int k=1;k<=n;k++){
				f[j][k]=min(f[j][k],f[j][x]+1+f[y][k]);//更新路径长度
			}
		}
		for(int j=1;j<n;j++){
			if(b[1][j]){
				ans=min(ans,d+f[j][n]);//更新答案
			}
		}
		a[x][y]=1;//更新a
		cnt=d;
	}
	cout<<ans<<endl;
	return 0;
}

T2:
注意空间,注意空间,注意空间!!!

一道我最爱的构造题,可惜挂成0分了!

思路:
先不管其他限制,把矩阵按照顺序排列好。

这时,如果是奇数就不管。

如果是偶数,每一行的数值是\(\frac{n+1}{2}\),每一列的数值是\(\frac{n^2-n+2}{2}\)

不管\(n\)是奇数还是偶数,每一列的数值是不变的整数,而每一行的数值在\(n\)为偶数时不成立。
因为我们要把每一行的数值改为整数,所以每一行的总和要加或减\(\frac{n}{2}\)的值。

可以把奇数行的最后一个与偶数行的\(n/2\)个交换,这样两行的数值都变为了整数。
\(n\% 4=0\)时,这两列交换了偶数次,两列的数值为整数,但当\(n\% 4=2\)时,两行交换了奇数次,不满足要求。

为了让这两列满足要求,我们在最后两行交换时,交换\(a[n-1][n-1],a[n][n/2-1]\)这样矛盾就转移到了第\(n-1\)列和第\(n/2-1\)列上,为了消除,交换这两列其他某一行的两个元素即可,这样两列同时加上,减去n,数值还是整数。

点击查看代码
#include<bits/stdc++.h>
#define re register
//Orz huaruoji Orz llmmkk Orz _Iva Orz pigonered 
using namespace std;
int sit[5][5]={{0,0,0,0,0},{0,1,2,3,6},{0,4,5,7,8},{0,10,9,11,14},{0,13,12,15,16}};
int a[2001][2001];
int n;
int main(){
//	freopen("s.in","r",stdin);
//	freopen("s.out","w",stdout);
	scanf("%d",&n);
	if(n==2||n==0){
		cout<<-1<<endl;
		return 0;
	}else if(n%2==1){
		for(re int i=1;i<=n;i++){
			for(re int j=1;j<=n;j++){
				cout<<(i-1)*n+j<<' ';
			}
			cout<<"\n";
		}
		return 0;
	}else if(n%4==0){
		for(re int i=1;i<=4;i++){
			for(re int j=1;j<=4;j++){
				a[i][j]=sit[(i-1)%4+1][(j-1)%4+1];
			}
		}
		for(re int i=1;i<=4;i++){
			for(re int j=5;j<=n;j++){
				a[i][j]=a[i][j-4]+16;
			}
		}
		for(int i=5;i<=n;i++){
			for(int j=1;j<=n;j++){
				a[i][j]=a[i-4][j]+16*(n/4);
			}
		}
		for(re int i=1;i<=n;i++){
			for(re int j=1;j<=n;j++){
				cout<<a[i][j]<<' ';
			}
			cout<<"\n";
		}
		//cout<<check()<<endl;
	}else if(n%2==0){//正解
		int cnt =1;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				a[i][j]=cnt++;
			}
		}
		for(int i=1;i<=n-3;i+=2){
			swap(a[i][n],a[i+1][n/2]);
		}
		swap(a[n-1][n-1],a[n][n/2-1]);
		swap(a[1][n-1],a[1][n/2-1]);
			for(re int i=1;i<=n;i++){
			for(re int j=1;j<=n;j++){
				cout<<a[i][j]<<' ';
			}
			cout<<"\n";
		}
		//cout<<check()<<endl;
	}
	
	return 0;
}

T3:

暴力可以有50分,可惜没时间了。

问题等价于从一个矩阵的\((x,y)\)向上和右上走,每走一步会有\(a_y\)的贡献,最小化贡献。
tj原文:

发现一个神奇的性质,就是最优策略一定是从\((x,y)\)走到某一列然后一直走到底因为如果
走到那一列更优那么一定会以最快的速度走到那一列,不可能出现转两次弯的情况,只能转一次弯。

证明:
如果转两次弯:

证明完成!
思路:

对于每一个询问\(x,y\),考虑在y前面的x个数中选出x个数的最小和,因为每个数是又\([i-1,j-1] and [i,j-1]\)更新而来的,要想最优就选一列走到底。

我们考虑最优的情况,第一个数选i个,后面的x-i个数就每个选1个,这样是最优的情况。

暴力是枚举每一列,然后算ans。有50分。

正解是使用线段树维护单调栈,找到单调递增的一个序列,在枚举序列上的每个点。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define in read()
using namespace std;
inline int read(){
	static char ch;
	int res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
const int N=1e6+100;
int n,Q;
int a[N],len,q[N],sum[N],lst[N];
int bin(int k){
	int l=1,r=len,mid;
	while(l<r){
		mid=l+r>>1;
		if(q[mid]<k){
			l=mid+1;
		}else{
			r=mid;
		}
	}
	return l;
}
int maxx[N*4];
void update(int k,int l,int r,int x,int v){
	if(l==r){
		maxx[k]=max(v,maxx[k]);
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid){
		update(k<<1,l,mid,x,v);
	}else{
		update(k<<1|1,mid+1,r,x,v);
	}
	maxx[k]=max(maxx[k<<1],maxx[k<<1|1]);
}
int query(int k,int l,int r,int x,int y){
	if(l>=x&&r<=y){
		return maxx[k];
	}
	int res=0;
	int mid=l+r>>1;
	if(x<=mid){
		res=max(res,query(k<<1,l,mid,x,y));
	}
	if(mid<y){
		res=max(res,query(k<<1|1,mid+1,r,x,y));
	}
	return res;
}
signed main(){
//	freopen("y2.in","r",stdin);
//	freopen("y.out","w",stdout);
	n=in,Q=in;
	for(int i=1;i<=n;i++){
		a[i]=in,q[i]=a[i];
		sum[i]=sum[i-1]+a[i];
	}
	sort(q+1,q+n+1);
	len=unique(q+1,q+n+1)-(q+1);
	for(int i=1;i<=n;i++){
		int qr=bin(a[i])-1;
		if(qr){
			lst[i]=query(1,1,n,1,qr);//单调栈的前一项
		}else{
			lst[i]=0;
		}
		update(1,1,n,qr+1,i);
	}
	while(Q--){
		int x=in,y=in;
		int ans=1e18;
		for(int i=y;i>0&&i>y-x;i=lst[i]){
			ans=min(ans,sum[y]-sum[i]+(x-y+i)*a[i]);//贡献
		}
		cout<<ans<<endl;
	}
	
	return 0;
}

T4 咕咕

posted @ 2021-11-11 21:11  SSZX_loser_lcy  阅读(32)  评论(0编辑  收藏  举报